diff --git a/README.md b/README.md index cf7a25ef4..d0672e6fb 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # DynamicalSystemFramework +[![Latest Release](https://img.shields.io/github/v/release/physycom/DynamicalSystemFramework)](https://github.com/physycom/DynamicalSystemFramework/releases/latest) [![Standard](https://img.shields.io/badge/C%2B%2B-20/23-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![TBB](https://img.shields.io/badge/TBB-C%2B%2B20%2F23-blue.svg)](https://github.com/oneapi-src/oneTBB) [![codecov](https://codecov.io/gh/physycom/DynamicalSystemFramework/graph/badge.svg?token=JV53J6IUJ3)](https://codecov.io/gh/physycom/DynamicalSystemFramework) -[![Latest Release](https://img.shields.io/github/v/release/physycom/DynamicalSystemFramework)](https://github.com/physycom/DynamicalSystemFramework/releases/latest) - The aim of this project is to rework the original [Traffic Flow Dynamics Model](https://github.com/Grufoony/TrafficFlowDynamicsModel). This rework consists of a full code rewriting, in order to implement more features (like *intersections*) and get advantage from the latest C++ updates. diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index b2de75490..d6336d2e2 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include "Network.hpp" #include "../utility/Logger.hpp" #include "../utility/Typedef.hpp" @@ -65,6 +67,7 @@ namespace dsm { network_t m_graph; protected: + tbb::task_arena m_taskArena; Time m_time; std::mt19937_64 m_generator; @@ -94,5 +97,6 @@ namespace dsm { if (seed.has_value()) { m_generator.seed(*seed); } + m_taskArena.initialize(); } }; // namespace dsm diff --git a/src/dsm/headers/RoadDynamics.hpp b/src/dsm/headers/RoadDynamics.hpp index ee8fcee1a..dae266975 100644 --- a/src/dsm/headers/RoadDynamics.hpp +++ b/src/dsm/headers/RoadDynamics.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -50,7 +51,7 @@ namespace dsm { std::unordered_map> m_turnCounts; std::unordered_map> m_turnMapping; std::unordered_map m_streetTails; - std::vector> m_travelDTs; + tbb::concurrent_vector> m_travelDTs; Time m_previousOptimizationTime, m_previousSpireTime; private: @@ -147,6 +148,7 @@ namespace dsm { /// @param nAgents The number of agents to add /// @param src_weights The weights of the source nodes /// @param dst_weights The weights of the destination nodes + /// @param minNodeDistance The minimum distance between the source and destination nodes /// @throw std::invalid_argument If the source and destination nodes are the same template requires(std::is_same_v> || @@ -154,9 +156,12 @@ namespace dsm { void addAgentsRandomly(Size nAgents, const TContainer& src_weights, const TContainer& dst_weights, - const size_t minNodeDistance = 0); + const std::variant + minNodeDistance = std::monostate{}); - void addAgentsRandomly(Size nAgents, const size_t minNodeDistance = 0); + void addAgentsRandomly(Size nAgents, + const std::variant + minNodeDistance = std::monostate{}); /// @brief Add an agent to the simulation /// @param agent std::unique_ptr to the agent @@ -798,19 +803,18 @@ namespace dsm { template requires(std::is_same_v> || std::is_same_v>) - void RoadDynamics::addAgentsRandomly(Size nAgents, - const TContainer& src_weights, - const TContainer& dst_weights, - const size_t minNodeDistance) { + void RoadDynamics::addAgentsRandomly( + Size nAgents, + const TContainer& src_weights, + const TContainer& dst_weights, + const std::variant minNodeDistance) { auto const& nSources{src_weights.size()}; auto const& nDestinations{dst_weights.size()}; Logger::debug( - std::format("Init addAgentsRandomly for {} agents from {} nodes to {} nodes with " - "minNodeDistance {}", + std::format("Init addAgentsRandomly for {} agents from {} nodes to {} nodes.", nAgents, nSources, - dst_weights.size(), - minNodeDistance)); + dst_weights.size())); if (nSources == 1 && nDestinations == 1 && src_weights.begin()->first == dst_weights.begin()->first) { throw std::invalid_argument(Logger::buildExceptionMessage( @@ -871,10 +875,23 @@ namespace dsm { if (this->itineraries().at(id)->path()->getRow(srcId).empty()) { continue; } - if (nDestinations > 1 && minNodeDistance > 0) { - // NOTE: Result must have a value in this case, so we can use value() as sort-of assertion + if (std::holds_alternative(minNodeDistance)) { + auto const minDistance{std::get(minNodeDistance)}; if (this->graph().shortestPath(srcId, id).value().path().size() < - minNodeDistance) { + minDistance) { + continue; + } + } else if (std::holds_alternative(minNodeDistance)) { + auto const minDistance{std::get(minNodeDistance)}; + if (this->graph() + .shortestPath(srcId, id, weight_functions::streetLength) + .value() + .distance() < minDistance) { + Logger::debug( + std::format("Skipping node {} because the distance from the source " + "is less than {}", + id, + minDistance)); continue; } } @@ -901,8 +918,8 @@ namespace dsm { template requires(is_numeric_v) - void RoadDynamics::addAgentsRandomly(Size nAgents, - const size_t minNodeDistance) { + void RoadDynamics::addAgentsRandomly( + Size nAgents, const std::variant minNodeDistance) { std::unordered_map src_weights, dst_weights; for (auto const& id : this->graph().inputNodes()) { src_weights[id] = 1.; @@ -967,99 +984,114 @@ namespace dsm { bool const bUpdateData = m_dataUpdatePeriod.has_value() && this->time() % m_dataUpdatePeriod.value() == 0; auto const N{this->graph().nNodes()}; - std::for_each( - this->graph().nodes().cbegin(), - this->graph().nodes().cend(), - [&](const auto& pair) { - for (auto const& sourceId : - this->graph().adjacencyMatrix().getCol(pair.first)) { - auto const streetId = sourceId * N + pair.first; - auto const& pStreet{this->graph().edge(streetId)}; - if (bUpdateData) { - m_streetTails[streetId] += pStreet->nExitingAgents(); - } - m_evolveStreet(pStreet, reinsert_agents); - - while (!pStreet->movingAgents().empty()) { - auto const& pAgent{pStreet->movingAgents().top()}; - if (pAgent->freeTime() < this->time()) { - break; - } - pAgent->setSpeed(0.); - auto const nLanes = pStreet->nLanes(); - bool bArrived{false}; - if (!pAgent->isRandom()) { - if (this->itineraries().at(pAgent->itineraryId())->destination() == - pStreet->target()) { - pAgent->updateItinerary(); - } - if (this->itineraries().at(pAgent->itineraryId())->destination() == - pStreet->target()) { - bArrived = true; + auto const numNodes{this->graph().nNodes()}; + const auto& nodes = + this->graph().nodes(); // assuming a container with contiguous indices + const unsigned int concurrency = std::thread::hardware_concurrency(); + // Calculate a grain size to partition the nodes into roughly "concurrency" blocks + const size_t grainSize = std::max(size_t(1), numNodes / concurrency); + this->m_taskArena.execute([&] { + tbb::parallel_for( + tbb::blocked_range(0, numNodes, grainSize), + [&](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i != range.end(); ++i) { + const auto& pNode = nodes.at(i); + for (auto const& sourceId : + this->graph().adjacencyMatrix().getCol(pNode->id())) { + auto const streetId = sourceId * N + pNode->id(); + auto const& pStreet{this->graph().edge(streetId)}; + if (bUpdateData) { + m_streetTails[streetId] += pStreet->nExitingAgents(); } - } - if (bArrived) { - std::uniform_int_distribution laneDist{ - 0, static_cast(nLanes - 1)}; - pStreet->enqueue(laneDist(this->m_generator)); - continue; - } - auto const nextStreetId = - this->m_nextStreetId(pAgent, pStreet->target(), pStreet->id()); - auto const& pNextStreet{this->graph().edge(nextStreetId)}; - pAgent->setNextStreetId(nextStreetId); - if (nLanes == 1) { - pStreet->enqueue(0); - continue; - } - auto const deltaAngle{pNextStreet->deltaAngle(pStreet->angle())}; - if (std::abs(deltaAngle) < std::numbers::pi) { - // Lanes are counted as 0 is the far right lane - if (std::abs(deltaAngle) < std::numbers::pi / 4) { - std::vector weights; - for (auto const& queue : pStreet->exitQueues()) { - weights.push_back(1. / (queue.size() + 1)); + m_evolveStreet(pStreet, reinsert_agents); + + while (!pStreet->movingAgents().empty()) { + auto const& pAgent{pStreet->movingAgents().top()}; + if (pAgent->freeTime() < this->time()) { + break; } - // If all weights are the same, make the last 0 - if (std::all_of(weights.begin(), weights.end(), [&](double w) { - return std::abs(w - weights.front()) < - std::numeric_limits::epsilon(); - })) { - weights.back() = 0.; - if (nLanes > 2) { - weights.front() = 0.; + pAgent->setSpeed(0.); + auto const nLanes = pStreet->nLanes(); + bool bArrived{false}; + if (!pAgent->isRandom()) { + if (this->itineraries().at(pAgent->itineraryId())->destination() == + pStreet->target()) { + pAgent->updateItinerary(); + } + if (this->itineraries().at(pAgent->itineraryId())->destination() == + pStreet->target()) { + bArrived = true; } } - // Normalize the weights - auto const sum = std::accumulate(weights.begin(), weights.end(), 0.); - for (auto& w : weights) { - w /= sum; + if (bArrived) { + std::uniform_int_distribution laneDist{ + 0, static_cast(nLanes - 1)}; + pStreet->enqueue(laneDist(this->m_generator)); + continue; + } + auto const nextStreetId = + this->m_nextStreetId(pAgent, pStreet->target(), pStreet->id()); + auto const& pNextStreet{this->graph().edge(nextStreetId)}; + pAgent->setNextStreetId(nextStreetId); + if (nLanes == 1) { + pStreet->enqueue(0); + continue; + } + auto const deltaAngle{pNextStreet->deltaAngle(pStreet->angle())}; + if (std::abs(deltaAngle) < std::numbers::pi) { + // Lanes are counted as 0 is the far right lane + if (std::abs(deltaAngle) < std::numbers::pi / 4) { + std::vector weights; + for (auto const& queue : pStreet->exitQueues()) { + weights.push_back(1. / (queue.size() + 1)); + } + // If all weights are the same, make the last 0 + if (std::all_of(weights.begin(), weights.end(), [&](double w) { + return std::abs(w - weights.front()) < + std::numeric_limits::epsilon(); + })) { + weights.back() = 0.; + if (nLanes > 2) { + weights.front() = 0.; + } + } + // Normalize the weights + auto const sum = + std::accumulate(weights.begin(), weights.end(), 0.); + for (auto& w : weights) { + w /= sum; + } + std::discrete_distribution laneDist{weights.begin(), + weights.end()}; + pStreet->enqueue(laneDist(this->m_generator)); + } else if (deltaAngle < 0.) { // Right + pStreet->enqueue(0); // Always the first lane + } else { // Left (deltaAngle > 0.) + pStreet->enqueue(nLanes - 1); // Always the last lane + } + } else { // U turn + pStreet->enqueue(nLanes - 1); // Always the last lane } - std::discrete_distribution laneDist{weights.begin(), - weights.end()}; - pStreet->enqueue(laneDist(this->m_generator)); - } else if (deltaAngle < 0.) { // Right - pStreet->enqueue(0); // Always the first lane - } else { // Left (deltaAngle > 0.) - pStreet->enqueue(nLanes - 1); // Always the last lane } - } else { // U turn - pStreet->enqueue(nLanes - 1); // Always the last lane } } - } - }); + }); + }); Logger::debug("Pre-nodes"); // Move transport capacity agents from each node - std::for_each(this->graph().nodes().cbegin(), - this->graph().nodes().cend(), - [&](const auto& pair) { - m_evolveNode(pair.second); - if (pair.second->isTrafficLight()) { - auto& tl = dynamic_cast(*pair.second); - ++tl; - } - }); + this->m_taskArena.execute([&] { + tbb::parallel_for(tbb::blocked_range(0, numNodes, grainSize), + [&](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i != range.end(); ++i) { + const auto& pNode = nodes.at(i); + m_evolveNode(pNode); + if (pNode->isTrafficLight()) { + auto& tl = dynamic_cast(*pNode); + ++tl; + } + } + }); + }); // cycle over agents and update their times std::uniform_int_distribution nodeDist{ 0, static_cast(this->graph().nNodes() - 1)}; @@ -1519,8 +1551,12 @@ namespace dsm { } if (bEmptyFile) { file << "time"; - for (auto const& [streetId, _] : this->graph().edges()) { - file << separator << streetId; + for (auto const& [streetId, pStreet] : this->graph().edges()) { + if (!pStreet->isSpire()) { + continue; + } + auto& spire = dynamic_cast(*pStreet); + file << separator << spire.code(); } file << std::endl; } @@ -1533,8 +1569,8 @@ namespace dsm { } else { value = dynamic_cast(*pStreet).outputCounts(reset); } + file << separator << value; } - file << separator << value; } file << std::endl; file.close(); @@ -1645,6 +1681,7 @@ namespace dsm { file << speed.mean << separator << speed.std << std::endl; m_travelDTs.clear(); } + m_travelDTs.clear(); file.close(); } }; // namespace dsm diff --git a/src/dsm/sources/RoadNetwork.cpp b/src/dsm/sources/RoadNetwork.cpp index 37c4398f2..003e83a96 100644 --- a/src/dsm/sources/RoadNetwork.cpp +++ b/src/dsm/sources/RoadNetwork.cpp @@ -642,8 +642,13 @@ namespace dsm { file << ";traffic_light"; } else if (pNode->isRoundabout()) { file << ";roundabout"; + } else if (std::find(m_inputNodes.begin(), m_inputNodes.end(), nodeId) != + m_inputNodes.end() || + std::find(m_outputNodes.begin(), m_outputNodes.end(), nodeId) != + m_outputNodes.end()) { + file << ";io"; } else { - file << ";intersection"; + file << ";"; } file << '\n'; } diff --git a/src/dsm/sources/TrafficLight.cpp b/src/dsm/sources/TrafficLight.cpp index a81cf6a95..21acf46da 100644 --- a/src/dsm/sources/TrafficLight.cpp +++ b/src/dsm/sources/TrafficLight.cpp @@ -127,12 +127,14 @@ namespace dsm { for (auto const& [streetId, cycles] : m_cycles) { if ((priorityStreets && m_streetPriorities.contains(streetId)) || (!priorityStreets && !m_streetPriorities.contains(streetId))) { - meanTime += std::accumulate(cycles.begin(), - cycles.end(), - 0., - [](double acc, TrafficLightCycle const& cycle) { - return acc + cycle.greenTime(); - }); + meanTime += + std::transform_reduce(cycles.begin(), + cycles.end(), + 0.0, // Initial value (double) + std::plus(), // Reduction function (addition) + [](const TrafficLightCycle& cycle) -> double { + return static_cast(cycle.greenTime()); + }); nCycles += cycles.size(); } }