diff --git a/benchmark/Dynamics/BenchDynamics.cpp b/benchmark/Dynamics/BenchDynamics.cpp index 432a64348..79136a329 100644 --- a/benchmark/Dynamics/BenchDynamics.cpp +++ b/benchmark/Dynamics/BenchDynamics.cpp @@ -2,7 +2,7 @@ #include "Graph.hpp" #include "Itinerary.hpp" -#include "Dynamics.hpp" +#include "FirstOrderDynamics.hpp" #include "Bench.hpp" using Graph = dsm::Graph; @@ -28,7 +28,6 @@ int main() { dynamics.addItinerary(it2); dynamics.addItinerary(it3); dynamics.addItinerary(it4); - dynamics.setSeed(69); dynamics.setErrorProbability(0.3); dynamics.setMinSpeedRateo(0.95); diff --git a/examples/slow_charge_rb.cpp b/examples/slow_charge_rb.cpp index efe40c698..f537c8415 100644 --- a/examples/slow_charge_rb.cpp +++ b/examples/slow_charge_rb.cpp @@ -120,7 +120,7 @@ int main(int argc, char** argv) { std::cout << "Creating dynamics...\n"; - Dynamics dynamics{graph}; + Dynamics dynamics{graph, SEED}; Unit n{0}; { std::vector destinationNodes; @@ -134,7 +134,6 @@ int main(int argc, char** argv) { } std::cout << "Number of exits: " << n << '\n'; - dynamics.setSeed(SEED); dynamics.setErrorProbability(0.05); dynamics.setMaxFlowPercentage(0.7707); // dynamics.setForcePriorities(true); diff --git a/examples/slow_charge_tl.cpp b/examples/slow_charge_tl.cpp index c796b69e8..499bc074a 100644 --- a/examples/slow_charge_tl.cpp +++ b/examples/slow_charge_tl.cpp @@ -216,7 +216,7 @@ int main(int argc, char** argv) { std::cout << "Creating dynamics...\n"; - Dynamics dynamics{graph}; + Dynamics dynamics{graph, SEED}; Unit n{0}; { std::vector destinationNodes; @@ -230,7 +230,6 @@ int main(int argc, char** argv) { } std::cout << "Number of exits: " << n << '\n'; - dynamics.setSeed(SEED); dynamics.setErrorProbability(ERROR_PROBABILITY); // dynamics.setMaxFlowPercentage(0.69); // dynamics.setForcePriorities(false); diff --git a/examples/stalingrado.cpp b/examples/stalingrado.cpp index d41825dfc..8b7db815a 100644 --- a/examples/stalingrado.cpp +++ b/examples/stalingrado.cpp @@ -82,8 +82,7 @@ int main() { std::cout << "Streets: " << graph.nEdges() << '\n'; // Create the dynamics - Dynamics dynamics{graph}; - dynamics.setSeed(69); + Dynamics dynamics{graph, 69}; dynamics.setMinSpeedRateo(0.95); dynamics.setSpeedFluctuationSTD(0.2); Itinerary itinerary{0, 4}; diff --git a/profiling/main.cpp b/profiling/main.cpp index b7c027ef6..268a0b210 100644 --- a/profiling/main.cpp +++ b/profiling/main.cpp @@ -41,7 +41,6 @@ int main() { dynamics.addItinerary(it2); dynamics.addItinerary(it3); dynamics.addItinerary(it4); - dynamics.setSeed(69); dynamics.setErrorProbability(0.3); dynamics.setMinSpeedRateo(0.95); dynamics.updatePaths(); diff --git a/src/dsm/dsm.hpp b/src/dsm/dsm.hpp index d63afdae3..c9aab8628 100644 --- a/src/dsm/dsm.hpp +++ b/src/dsm/dsm.hpp @@ -25,7 +25,7 @@ namespace dsm { #include "headers/Roundabout.hpp" #include "headers/SparseMatrix.hpp" #include "headers/Street.hpp" -#include "headers/Dynamics.hpp" +#include "headers/FirstOrderDynamics.hpp" #include "utility/TypeTraits/is_node.hpp" #include "utility/TypeTraits/is_street.hpp" #include "utility/TypeTraits/is_numeric.hpp" diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index 45525590f..f96b8fc44 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -44,8 +44,10 @@ namespace dsm { Measurement(T mean, T std) : mean{mean}, std{std} {} Measurement(std::span data) { - auto x_mean = T{}, x2_mean = T{}; + auto x_mean = static_cast(0), x2_mean = static_cast(0); if (data.empty()) { + mean = static_cast(0); + std = static_cast(0); return; } @@ -67,46 +69,16 @@ namespace dsm { protected: std::unordered_map> m_itineraries; std::map>> m_agents; - Time m_time, m_previousSpireTime, m_previousOptimizationTime; Graph m_graph; - double m_errorProbability; - double m_minSpeedRateo; - double m_maxFlowPercentage; - mutable std::mt19937_64 m_generator{std::random_device{}()}; - std::vector m_travelTimes; - std::unordered_map m_agentNextStreetId; - bool m_forcePriorities; - std::optional m_dataUpdatePeriod; - std::unordered_map> m_turnCounts; - std::unordered_map> m_turnMapping; - std::unordered_map m_streetTails; + Time m_time, m_previousSpireTime; + std::mt19937_64 m_generator; - /// @brief Get the next street id - /// @param agentId The id of the agent - /// @param NodeId The id of the node - /// @param streetId The id of the incoming street - /// @return Id The id of the randomly selected next street - virtual Id m_nextStreetId(Id agentId, - Id NodeId, - std::optional streetId = std::nullopt); - /// @brief Increase the turn counts - virtual void m_increaseTurnCounts(Id streetId, double delta); - /// @brief Evolve a street - /// @param pStreet A std::unique_ptr to the street - /// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination - /// @details If possible, removes the first agent of the street's queue, putting it in the destination node. - /// If the agent is going into the destination node, it is removed from the simulation (and then reinserted if reinsert_agents is true) virtual void m_evolveStreet(const std::unique_ptr& pStreet, - bool reinsert_agents); - /// @brief If possible, removes one agent from the node, putting it on the next street. - /// @param pNode A std::unique_ptr to the node - /// @return bool True if the agent has been moved, false otherwise - virtual bool m_evolveNode(const std::unique_ptr& pNode); - /// @brief Evolve the agents. - /// @details Puts all new agents on a street, if possible, decrements all delays - /// and increments all travel times. - virtual void m_evolveAgents(); - /// @brief Update the path of a single itinerary + bool reinsert_agents) = 0; + virtual bool m_evolveNode(const std::unique_ptr& pNode) = 0; + virtual void m_evolveAgents() = 0; + + /// @brief Update the path of a single itinerary using Dijsktra's algorithm /// @param pItinerary An std::unique_prt to the itinerary void m_updatePath(const std::unique_ptr& pItinerary) { const Size dimension = m_graph.adjMatrix().getRowDim(); @@ -160,88 +132,20 @@ namespace dsm { public: /// @brief Construct a new Dynamics object /// @param graph The graph representing the network - Dynamics(Graph& graph); + Dynamics(Graph& graph, std::optional seed); + + virtual void setAgentSpeed(Size agentId) = 0; + virtual void evolve(bool reinsert_agents = false) = 0; + + /// @brief Update the paths of the itineraries based on the actual travel times + virtual void updatePaths(); - /// @brief Set the itineraries - /// @param itineraries The itineraries - void setItineraries(std::span itineraries); - /// @brief Set the seed for the graph's random number generator - /// @param seed The seed - void setSeed(unsigned int seed) { m_generator.seed(seed); }; - /// @brief Set the minim speed rateo, i.e. the minim speed with respect to the speed limit - /// @param minSpeedRateo The minim speed rateo - /// @throw std::invalid_argument If the minim speed rateo is not between 0 and 1 - void setMinSpeedRateo(double minSpeedRateo); - /// @brief Set the error probability - /// @param errorProbability The error probability - /// @throw std::invalid_argument If the error probability is not between 0 and 1 - void setErrorProbability(double errorProbability); - /// @brief Set the maximum flow percentage - /// @param maxFlowPercentage The maximum flow percentage - /// @details The maximum flow percentage is the percentage of the maximum flow that a street can transmit. Default is 1 (100%). - /// @throw std::invalid_argument If the maximum flow percentage is not between 0 and 1 - void setMaxFlowPercentage(double maxFlowPercentage); /// @brief Set the dynamics destination nodes auto-generating itineraries /// @param destinationNodes The destination nodes /// @param updatePaths If true, the paths are updated /// @throws std::invalid_argument Ifone or more destination nodes do not exist void setDestinationNodes(const std::span& destinationNodes, bool updatePaths = true); - /// @brief Set the speed of an agent - /// @details This is a pure-virtual function, it must be implemented in the derived classes - /// @param agentId The id of the agent - virtual void setAgentSpeed(Size agentId) = 0; - /// @brief Set the force priorities flag - /// @param forcePriorities The flag - /// @details If true, if an agent cannot move to the next street, the whole node is skipped - void setForcePriorities(bool forcePriorities) { m_forcePriorities = forcePriorities; } - /// @brief Set the data update period. - /// @param dataUpdatePeriod delay_t, The period - /// @details Some data, i.e. the street queue lengths, are stored only after a fixed amount of time which is represented by this variable. - void setDataUpdatePeriod(delay_t dataUpdatePeriod) { - m_dataUpdatePeriod = dataUpdatePeriod; - } - - /// @brief Update the paths of the itineraries based on the actual travel times - virtual void updatePaths(); - /// @brief Evolve the simulation - /// @details Evolve the simulation by moving the agents and updating the travel times. - /// In particular: - /// - Move the first agent of each street queue, if possible, putting it in the next node - /// - Move the agents from each node, if possible, putting them in the next street and giving them a speed. - /// If the error probability is not zero, the agents can move to a random street. - /// If the agent is in the destination node, it is removed from the simulation (and then reinserted if reinsert_agents is true) - /// - Cycle over agents and update their times - /// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination - virtual void evolve(bool reinsert_agents = false); - /// @brief Optimize the traffic lights by changing the green and red times - /// @param threshold double, The percentage of the mean capacity of the streets used as threshold for the delta between the two tails. - /// @param densityTolerance double, The algorithm will consider all streets with density up to densityTolerance*meanDensity - /// @param optimizationType TrafficLightOptimization, The type of optimization. Default is DOUBLE_TAIL - /// @details The function cycles over the traffic lights and, if the difference between the two tails is greater than - /// 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. - /// The optimizationType parameter can be set to SINGLE_TAIL to use an algorith which looks only at the incoming street tails or to DOUBLE_TAIL to consider both incoming and outgoing street tails. - void optimizeTrafficLights(double const threshold = 0., - double const densityTolerance = 0., - TrafficLightOptimization optimizationType = - TrafficLightOptimization::DOUBLE_TAIL); - - /// @brief Get the graph - /// @return const Graph&, The graph - const Graph& graph() const { return m_graph; }; - /// @brief Get the itineraries - /// @return const std::unordered_map&, The itineraries - const std::unordered_map>& itineraries() const { - return m_itineraries; - } - /// @brief Get the agents - /// @return const std::unordered_map>&, The agents - const std::map>>& agents() const { - return m_agents; - } - /// @brief Get the time - /// @return Time The time - Time time() const { return m_time; } /// @brief Add an agent to the simulation /// @param agent The agent @@ -324,6 +228,23 @@ namespace dsm { /// @brief Reset the simulation time void resetTime(); + /// @brief Get the graph + /// @return const Graph&, The graph + const Graph& graph() const { return m_graph; }; + /// @brief Get the itineraries + /// @return const std::unordered_map&, The itineraries + const std::unordered_map>& itineraries() const { + return m_itineraries; + } + /// @brief Get the agents + /// @return const std::unordered_map>&, The agents + const std::map>>& agents() const { + return m_agents; + } + /// @brief Get the time + /// @return Time The time + Time time() const { return m_time; } + /// @brief Get the mean speed of the agents in \f$m/s\f$ /// @return Measurement The mean speed of the agents and the standard deviation Measurement agentMeanSpeed() const; @@ -358,356 +279,17 @@ namespace dsm { /// @param clearData If true, the travel times are cleared after the computation /// @return Measurement The mean travel time of the agents and the standard Measurement meanTravelTime(bool clearData = false); - /// @brief Get the turn counts of the agents - /// @return const std::array& The turn counts - /// @details The array contains the counts of left (0), straight (1), right (2) and U (3) turns - const std::unordered_map>& turnCounts() const { - return m_turnCounts; - } - /// @brief Get the turn probabilities of the agents - /// @return std::array The turn probabilities - /// @details The array contains the probabilities of left (0), straight (1), right (2) and U (3) turns - std::unordered_map> turnProbabilities(bool reset = true); - - std::unordered_map> turnMapping() const { - return m_turnMapping; - } }; template requires(is_numeric_v) - Dynamics::Dynamics(Graph& graph) - : m_time{0}, + Dynamics::Dynamics(Graph& graph, std::optional seed) + : m_graph{std::move(graph)}, + m_time{0}, m_previousSpireTime{0}, - m_previousOptimizationTime{0}, - m_graph{std::move(graph)}, - m_errorProbability{0.}, - m_minSpeedRateo{0.}, - m_maxFlowPercentage{1.}, - m_forcePriorities{false} { - for (const auto& [streetId, street] : m_graph.streetSet()) { - m_streetTails.emplace(streetId, 0); - m_turnCounts.emplace(streetId, std::array{0, 0, 0, 0}); - // fill turn mapping as [streetId, [left street Id, straight street Id, right street Id, U self street Id]] - m_turnMapping.emplace(streetId, std::array{-1, -1, -1, -1}); - // Turn mappings - const auto& srcNodeId = street->nodePair().second; - for (const auto& [ss, _] : m_graph.adjMatrix().getRow(srcNodeId, true)) { - const auto& delta = street->angle() - m_graph.streetSet()[ss]->angle(); - if (std::abs(delta) < std::numbers::pi) { - if (delta < 0.) { - m_turnMapping[streetId][dsm::Direction::RIGHT] = ss; - ; // right - } else if (delta > 0.) { - m_turnMapping[streetId][dsm::Direction::LEFT] = ss; // left - } else { - m_turnMapping[streetId][dsm::Direction::STRAIGHT] = ss; // straight - } - } else { - m_turnMapping[streetId][dsm::Direction::UTURN] = ss; // U - } - } - } - } - - template - requires(is_numeric_v) - Id Dynamics::m_nextStreetId(Id agentId, - Id nodeId, - std::optional streetId) { - auto possibleMoves = m_graph.adjMatrix().getRow(nodeId, true); - std::uniform_real_distribution uniformDist{0., 1.}; - if (m_itineraries.size() > 0 && uniformDist(m_generator) > m_errorProbability) { - const auto& it = m_itineraries[m_agents[agentId]->itineraryId()]; - if (it->destination() != nodeId) { - possibleMoves = it->path().getRow(nodeId, true); - } - } - assert(possibleMoves.size() > 0); - std::uniform_int_distribution moveDist{ - 0, static_cast(possibleMoves.size() - 1)}; - uint8_t p{0}; - auto iterator = possibleMoves.begin(); - // while loop to avoid U turns in non-roundabout junctions - do { - p = moveDist(m_generator); - iterator = possibleMoves.begin(); - std::advance(iterator, p); - } while (!m_graph.nodeSet().at(nodeId)->isRoundabout() and streetId.has_value() and - (m_graph.streetSet()[iterator->first]->nodePair().second == - m_graph.streetSet()[streetId.value()]->nodePair().first) and - (possibleMoves.size() > 1)); - return iterator->first; - } - - template - requires(is_numeric_v) - void Dynamics::m_increaseTurnCounts(Id streetId, double delta) { - if (std::abs(delta) < std::numbers::pi) { - if (delta < 0.) { - ++m_turnCounts[streetId][0]; // right - } else if (delta > 0.) { - ++m_turnCounts[streetId][2]; // left - } else { - ++m_turnCounts[streetId][1]; // straight - } - } else { - ++m_turnCounts[streetId][3]; // U - } - } - - template - requires(is_numeric_v) - void Dynamics::m_evolveStreet(const std::unique_ptr& pStreet, - bool reinsert_agents) { - auto const nLanes = pStreet->nLanes(); - std::uniform_real_distribution uniformDist{0., 1.}; - for (auto queueIndex = 0; queueIndex < nLanes; ++queueIndex) { - if (uniformDist(m_generator) > m_maxFlowPercentage || - pStreet->queue(queueIndex).empty()) { - continue; - } - const auto agentId{pStreet->queue(queueIndex).front()}; - if (m_agents[agentId]->delay() > 0) { - continue; - } - m_agents[agentId]->setSpeed(0.); - const auto& destinationNode{m_graph.nodeSet()[pStreet->nodePair().second]}; - if (destinationNode->isFull()) { - continue; - } - if (destinationNode->isTrafficLight()) { - auto& tl = dynamic_cast(*destinationNode); - auto const direction{pStreet->laneMapping().at(queueIndex)}; - if (!tl.isGreen(pStreet->id(), direction)) { - continue; - } - } - if (destinationNode->id() == - m_itineraries[m_agents[agentId]->itineraryId()]->destination()) { - pStreet->dequeue(queueIndex); - m_travelTimes.push_back(m_agents[agentId]->time()); - if (reinsert_agents) { - // take last agent id in map - Agent newAgent{static_cast(m_agents.rbegin()->first + 1), - m_agents[agentId]->itineraryId(), - m_agents[agentId]->srcNodeId().value()}; - if (m_agents[agentId]->srcNodeId().has_value()) { - newAgent.setSourceNodeId(m_agents[agentId]->srcNodeId().value()); - } - this->removeAgent(agentId); - this->addAgent(newAgent); - } else { - this->removeAgent(agentId); - } - continue; - } - auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]}; - if (nextStreet->isFull()) { - continue; - } - pStreet->dequeue(queueIndex); - assert(destinationNode->id() == nextStreet->nodePair().first); - if (destinationNode->isIntersection()) { - auto& intersection = dynamic_cast(*destinationNode); - auto const delta{nextStreet->deltaAngle(pStreet->angle())}; - m_increaseTurnCounts(pStreet->id(), delta); - intersection.addAgent(delta, agentId); - } else if (destinationNode->isRoundabout()) { - auto& roundabout = dynamic_cast(*destinationNode); - roundabout.enqueue(agentId); - } - } - } - - template - requires(is_numeric_v) - bool Dynamics::m_evolveNode(const std::unique_ptr& pNode) { - if (pNode->isIntersection()) { - auto& intersection = dynamic_cast(*pNode); - if (intersection.agents().empty()) { - return false; - } - for (auto const [angle, agentId] : intersection.agents()) { - auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]}; - if (nextStreet->isFull()) { - if (m_forcePriorities) { - return false; - } - continue; - } - intersection.removeAgent(agentId); - m_agents[agentId]->setStreetId(nextStreet->id()); - this->setAgentSpeed(agentId); - m_agents[agentId]->incrementDelay( - std::ceil(nextStreet->length() / m_agents[agentId]->speed())); - nextStreet->addAgent(agentId); - m_agentNextStreetId.erase(agentId); - return true; - } - return false; - } else if (pNode->isRoundabout()) { - auto& roundabout = dynamic_cast(*pNode); - if (roundabout.agents().empty()) { - return false; - } - auto const agentId{roundabout.agents().front()}; - auto const& nextStreet{m_graph.streetSet()[m_agentNextStreetId[agentId]]}; - if (!(nextStreet->isFull())) { - if (m_agents[agentId]->streetId().has_value()) { - const auto streetId = m_agents[agentId]->streetId().value(); - auto delta = nextStreet->angle() - m_graph.streetSet()[streetId]->angle(); - if (delta > std::numbers::pi) { - delta -= 2 * std::numbers::pi; - } else if (delta < -std::numbers::pi) { - delta += 2 * std::numbers::pi; - } - m_increaseTurnCounts(streetId, delta); - } - roundabout.dequeue(); - m_agents[agentId]->setStreetId(nextStreet->id()); - this->setAgentSpeed(agentId); - m_agents[agentId]->incrementDelay( - std::ceil(nextStreet->length() / m_agents[agentId]->speed())); - nextStreet->addAgent(agentId); - m_agentNextStreetId.erase(agentId); - } else { - return false; - } - } - return true; - } - - template - requires(is_numeric_v) - void Dynamics::m_evolveAgents() { - for (const auto& [agentId, agent] : m_agents) { - if (agent->delay() > 0) { - const auto& street{m_graph.streetSet()[agent->streetId().value()]}; - if (agent->delay() > 1) { - agent->incrementDistance(); - } else { - double distance{std::fmod(street->length(), agent->speed())}; - if (distance < std::numeric_limits::epsilon()) { - agent->incrementDistance(); - } else { - agent->incrementDistance(distance); - } - } - agent->decrementDelay(); - if (agent->delay() == 0) { - auto const nLanes = street->nLanes(); - if (m_itineraries[agent->itineraryId()]->destination() == - street->nodePair().second) { - std::uniform_int_distribution laneDist{ - 0, static_cast(nLanes - 1)}; - street->enqueue(agentId, laneDist(m_generator)); - } else { - auto const nextStreetId = - this->m_nextStreetId(agentId, street->nodePair().second, street->id()); - auto const& pNextStreet{m_graph.streetSet()[nextStreetId]}; - m_agentNextStreetId.emplace(agentId, nextStreetId); - if (nLanes == 1) { - street->enqueue(agentId, 0); - } else { - auto const deltaAngle{pNextStreet->deltaAngle(street->angle())}; - if (std::abs(deltaAngle) < std::numbers::pi) { - // Lanes are counted as 0 is the far right lane - if (deltaAngle < 0.) { // Right - street->enqueue(agentId, 0); // Always the first lane - } else if (deltaAngle > 0.) { // Left - street->enqueue(agentId, nLanes - 1); // Always the last lane - } else { // Straight - std::uniform_int_distribution laneDist{ - 0, static_cast(nLanes - 2)}; - street->enqueue(agentId, laneDist(m_generator)); - } - } else { // U turn - street->enqueue(agentId, nLanes - 1); // Always the last lane - } - } - } - } - } else if (!agent->streetId().has_value() && - !m_agentNextStreetId.contains(agentId)) { - assert(agent->srcNodeId().has_value()); - const auto& srcNode{m_graph.nodeSet()[agent->srcNodeId().value()]}; - if (srcNode->isFull()) { - continue; - } - const auto& nextStreet{ - m_graph.streetSet()[this->m_nextStreetId(agentId, srcNode->id())]}; - if (nextStreet->isFull()) { - continue; - } - assert(srcNode->id() == nextStreet->nodePair().first); - if (srcNode->isIntersection()) { - auto& intersection = dynamic_cast(*srcNode); - intersection.addAgent(0., agentId); - } else if (srcNode->isRoundabout()) { - auto& roundabout = dynamic_cast(*srcNode); - roundabout.enqueue(agentId); - } - m_agentNextStreetId.emplace(agentId, nextStreet->id()); - } else if (agent->delay() == 0) { - agent->setSpeed(0.); - } - agent->incrementTime(); - } - } - - template - requires(is_numeric_v) - void Dynamics::setItineraries(std::span itineraries) { - std::ranges::for_each(itineraries, [this](const auto& itinerary) { - m_itineraries.insert(std::make_unique(itinerary)); - }); - } - - template - requires(is_numeric_v) - void Dynamics::setMinSpeedRateo(double minSpeedRateo) { - if (minSpeedRateo < 0. || minSpeedRateo > 1.) { - throw std::invalid_argument(buildLog(std::format( - "The minimum speed rateo ({}) must be between 0 and 1", minSpeedRateo))); - } - m_minSpeedRateo = minSpeedRateo; - } - - template - requires(is_numeric_v) - void Dynamics::setErrorProbability(double errorProbability) { - if (errorProbability < 0. || errorProbability > 1.) { - throw std::invalid_argument(buildLog(std::format( - "The error probability ({}) must be between 0 and 1", errorProbability))); - } - m_errorProbability = errorProbability; - } - - template - requires(is_numeric_v) - void Dynamics::setMaxFlowPercentage(double maxFlowPercentage) { - if (maxFlowPercentage < 0. || maxFlowPercentage > 1.) { - throw std::invalid_argument( - buildLog(std::format("The maximum flow percentage ({}) must be between 0 and 1", - maxFlowPercentage))); - } - m_maxFlowPercentage = maxFlowPercentage; - } - - template - requires(is_numeric_v) - void Dynamics::setDestinationNodes(const std::span& destinationNodes, - bool updatePaths) { - for (const auto& nodeId : destinationNodes) { - if (!m_graph.nodeSet().contains(nodeId)) { - throw std::invalid_argument( - buildLog(std::format("Node with id {} not found", nodeId))); - } - this->addItinerary(Itinerary{nodeId, nodeId}); - } - if (updatePaths) { - this->updatePaths(); + m_generator{std::random_device{}()} { + if (seed.has_value()) { + m_generator.seed(seed.value()); } } @@ -737,154 +319,18 @@ namespace dsm { template requires(is_numeric_v) - void Dynamics::evolve(bool reinsert_agents) { - // move the first agent of each street queue, if possible, putting it in the next node - bool const bUpdateData = - m_dataUpdatePeriod.has_value() && m_time % m_dataUpdatePeriod.value() == 0; - for (const auto& [streetId, pStreet] : m_graph.streetSet()) { - if (bUpdateData) { - m_streetTails[streetId] += pStreet->nExitingAgents(); - } - for (auto i = 0; i < pStreet->transportCapacity(); ++i) { - this->m_evolveStreet(pStreet, reinsert_agents); - } - } - // Move transport capacity agents from each node - for (const auto& [nodeId, pNode] : m_graph.nodeSet()) { - for (auto i = 0; i < pNode->transportCapacity(); ++i) { - if (!this->m_evolveNode(pNode)) { - break; - } - } - if (pNode->isTrafficLight()) { - auto& tl = dynamic_cast(*pNode); - ++tl; // Increment the counter - } - } - // cycle over agents and update their times - this->m_evolveAgents(); - // increment time simulation - ++m_time; - } - - template - requires(is_numeric_v) - void Dynamics::optimizeTrafficLights( - double const threshold, - double const densityTolerance, - TrafficLightOptimization const optimizationType) { - if (threshold < 0 || threshold > 1) { - throw std::invalid_argument( - buildLog(std::format("The threshold parameter is a percentage and must be " - "bounded between 0-1. Inserted value: {}", - threshold))); - } - if (densityTolerance < 0 || densityTolerance > 1) { - throw std::invalid_argument(buildLog( - std::format("The densityTolerance parameter is a percentage and must be " - "bounded between 0-1. Inserted value: {}", - densityTolerance))); - } - auto const meanDensityGlob = streetMeanDensity().mean; // Measurement - for (const auto& [nodeId, pNode] : m_graph.nodeSet()) { - if (!pNode->isTrafficLight()) { - continue; - } - auto& tl = dynamic_cast(*pNode); - const auto& streetPriorities = tl.streetPriorities(); - Size greenSum{0}, greenQueue{0}; - Size redSum{0}, redQueue{0}; - for (const auto& [streetId, _] : m_graph.adjMatrix().getCol(nodeId, true)) { - if (streetPriorities.contains(streetId)) { - greenSum += m_streetTails[streetId]; - for (auto const& queue : m_graph.streetSet()[streetId]->exitQueues()) { - greenQueue += queue.size(); - } - } else { - redSum += m_streetTails[streetId]; - for (auto const& queue : m_graph.streetSet()[streetId]->exitQueues()) { - redQueue += queue.size(); - } - } - } - const auto nCycles = static_cast(m_time - m_previousOptimizationTime) / - m_dataUpdatePeriod.value(); - const delay_t delta = - std::floor(std::fabs(static_cast(greenQueue - redQueue)) / nCycles); - // std::cout << std::format("GreenSum: {}, RedSum: {}, Delta: {}, nCycles: {}\n", - // greenQueue, redQueue, delta, nCycles); - if (delta == 0) { - continue; - } - const Size smallest = std::min(greenSum, redSum); - // std::cout << std::format("GreenSum: {}, RedSum: {}, Smallest: {}\n", greenSum, redSum, smallest); - // std::cout << std::format("Diff: {}, Threshold * Smallest: {}\n", std::abs(static_cast(greenSum - redSum)), threshold * smallest); - if (std::abs(static_cast(greenSum - redSum)) < threshold * smallest) { - tl.resetCycles(); - continue; - } - auto const greenTime = tl.maxGreenTime(true); - auto const redTime = tl.maxGreenTime(false); - if (optimizationType == TrafficLightOptimization::SINGLE_TAIL) { - if ((greenSum > redSum) && !(greenTime > redTime) && (redTime > delta) && - (greenQueue > redQueue)) { - tl.increaseGreenTimes(delta); - } else if ((redSum > greenSum) && !(redTime > greenTime) && (greenTime > delta) && - (redQueue > greenQueue)) { - tl.decreaseGreenTimes(delta); - } else { - tl.resetCycles(); - } - } else if (optimizationType == TrafficLightOptimization::DOUBLE_TAIL) { - // If the difference is not less than the threshold - // - 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 - // - If the previous check fails, do nothing - double meanDensity_streets{0.}; - { - // Store the ids of outgoing streets - const auto& row{m_graph.adjMatrix().getRow(nodeId, true)}; - for (const auto& [streetId, _] : row) { - meanDensity_streets += m_graph.streetSet()[streetId]->density(); - } - // Take the mean density of the outgoing streets - const auto nStreets = row.size(); - if (nStreets > 1) { - meanDensity_streets /= nStreets; - } - } - auto const ratio = meanDensityGlob / meanDensity_streets; - // densityTolerance represents the max border we want to consider - auto const dyn_thresh = std::tanh(ratio) * densityTolerance; - if (meanDensityGlob * (1. + dyn_thresh) > meanDensity_streets) { - if (meanDensityGlob > meanDensity_streets) { - // Smaller than max density - if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { - tl.decreaseGreenTimes(delta); - } else if (!(redTime < greenTime) && (greenSum > redSum) && - (redTime > delta)) { - tl.increaseGreenTimes(delta); - } else { - tl.resetCycles(); - } - } else { - // Greater than max density - if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { - tl.decreaseGreenTimes(delta * dyn_thresh); - } else if (!(redTime < greenTime) && (greenSum > redSum) && - (redTime > delta)) { - tl.increaseGreenTimes(delta * dyn_thresh); - } else { - tl.resetCycles(); - } - } - } + void Dynamics::setDestinationNodes(const std::span& destinationNodes, + bool updatePaths) { + for (const auto& nodeId : destinationNodes) { + if (!m_graph.nodeSet().contains(nodeId)) { + throw std::invalid_argument( + buildLog(std::format("Node with id {} not found", nodeId))); } + this->addItinerary(Itinerary{nodeId, nodeId}); } - // Cleaning variables - for (auto& [id, element] : m_streetTails) { - element = 0; + if (updatePaths) { + this->updatePaths(); } - m_previousOptimizationTime = m_time; } template @@ -1179,28 +625,14 @@ namespace dsm { template requires(is_numeric_v) Measurement Dynamics::agentMeanSpeed() const { - if (m_agents.size() == 0) { - return Measurement(0., 0.); - } - const double mean{std::accumulate(m_agents.cbegin(), - m_agents.cend(), - 0., - [](double sum, const auto& agent) { - return sum + agent.second->speed(); - }) / - m_agents.size()}; - if (m_agents.size() == 1) { - return Measurement(mean, 0.); + std::vector speeds; + if (!m_agents.empty()) { + speeds.reserve(m_agents.size()); + for (const auto& [agentId, agent] : m_agents) { + speeds.push_back(agent->speed()); + } } - const double variance{ - std::accumulate(m_agents.cbegin(), - m_agents.cend(), - 0., - [mean](double sum, const auto& agent) { - return sum + std::pow(agent.second->speed() - mean, 2); - }) / - (m_agents.size() - 1)}; - return Measurement(mean, std::sqrt(variance)); + return Measurement(speeds); } template @@ -1273,7 +705,7 @@ namespace dsm { m_previousSpireTime = m_time; std::vector flows; flows.reserve(m_graph.streetSet().size()); - for (const auto& [streetId, street] : m_graph.streetSet()) { + for (auto const& [streetId, street] : m_graph.streetSet()) { if (street->isSpire()) { auto& spire = dynamic_cast(*street); flows.push_back(static_cast(spire.outputCounts(resetValue)) / deltaTime); @@ -1281,203 +713,4 @@ namespace dsm { } return Measurement(flows); } - - template - requires(is_numeric_v) - Measurement Dynamics::meanTravelTime(bool clearData) { - if (m_travelTimes.size() == 0) { - return Measurement(0., 0.); - } - const double mean{std::accumulate(m_travelTimes.cbegin(), m_travelTimes.cend(), 0.) / - m_travelTimes.size()}; - const double variance{std::accumulate(m_travelTimes.cbegin(), - m_travelTimes.cend(), - 0., - [mean](double sum, const auto& travelTime) { - return sum + std::pow(travelTime - mean, 2); - }) / - (m_travelTimes.size() - 1)}; - if (clearData) { - m_travelTimes.clear(); - } - return Measurement(mean, std::sqrt(variance)); - } - - template - requires(is_numeric_v) - std::unordered_map> Dynamics::turnProbabilities( - bool reset) { - std::unordered_map> res; - for (auto& [streetId, counts] : m_turnCounts) { - std::array probabilities{0., 0., 0., 0.}; - const auto sum{std::accumulate(counts.cbegin(), counts.cend(), 0.)}; - if (sum != 0) { - for (auto i{0}; i < counts.size(); ++i) { - probabilities[i] = counts[i] / sum; - } - } - res.emplace(streetId, probabilities); - } - if (reset) { - for (auto& [streetId, counts] : m_turnCounts) { - std::fill(counts.begin(), counts.end(), 0); - } - } - return res; - } - - template - requires(std::unsigned_integral) - class FirstOrderDynamics : public Dynamics { - double m_speedFluctuationSTD; - - public: - /// @brief Construct a new First Order Dynamics object - /// @param graph, The graph representing the network - FirstOrderDynamics(Graph& graph) - : Dynamics(graph), m_speedFluctuationSTD{0.} {}; - /// @brief Set the speed of an agent - /// @param agentId The id of the agent - /// @throw std::invalid_argument, If the agent is not found - void setAgentSpeed(Size agentId) override; - /// @brief Set the standard deviation of the speed fluctuation - /// @param speedFluctuationSTD The standard deviation of the speed fluctuation - /// @throw std::invalid_argument, If the standard deviation is negative - void setSpeedFluctuationSTD(double speedFluctuationSTD); - /// @brief Get the mean speed of a street in \f$m/s\f$ - /// @return double The mean speed of the street or street->maxSpeed() if the street is empty - /// @details The mean speed of a street is given by the formula: - /// \f$ v_{\text{mean}} = v_{\text{max}} \left(1 - \frac{\alpha}{2} \left( n - 1\right) \right) \f$ - /// where \f$ v_{\text{max}} \f$ is the maximum speed of the street, \f$ \alpha \f$ is the minimum speed rateo divided by the capacity - /// and \f$ n \f$ is the number of agents in the street - double streetMeanSpeed(Id streetId) const override; - /// @brief Get the mean speed of the streets in \f$m/s\f$ - /// @return Measurement The mean speed of the agents and the standard deviation - Measurement streetMeanSpeed() const override; - /// @brief Get the mean speed of the streets with density above or below a threshold in \f$m/s\f$ - /// @param threshold The density threshold to consider - /// @param above If true, the function returns the mean speed of the streets with a density above the threshold, otherwise below - /// @return Measurement The mean speed of the agents and the standard deviation - Measurement streetMeanSpeed(double threshold, bool above) const override; - }; - - template - requires(std::unsigned_integral) - void FirstOrderDynamics::setAgentSpeed(Size agentId) { - const auto& agent{this->m_agents[agentId]}; - const auto& street{this->m_graph.streetSet()[agent->streetId().value()]}; - double speed{street->maxSpeed() * - (1. - this->m_minSpeedRateo * street->density(true))}; - if (m_speedFluctuationSTD > 0.) { - std::normal_distribution speedDist{speed, speed * m_speedFluctuationSTD}; - speed = speedDist(this->m_generator); - } - speed < 0. ? agent->setSpeed(street->maxSpeed() * (1. - this->m_minSpeedRateo)) - : agent->setSpeed(speed); - } - - template - requires(std::unsigned_integral) - void FirstOrderDynamics::setSpeedFluctuationSTD(double speedFluctuationSTD) { - if (speedFluctuationSTD < 0.) { - throw std::invalid_argument( - buildLog("The speed fluctuation standard deviation must be positive.")); - } - m_speedFluctuationSTD = speedFluctuationSTD; - } - - template - requires(std::unsigned_integral) - double FirstOrderDynamics::streetMeanSpeed(Id streetId) const { - const auto& street{this->m_graph.streetSet().at(streetId)}; - if (street->nAgents() == 0) { - return street->maxSpeed(); - } - double meanSpeed{0.}; - Size n{0}; - if (street->nExitingAgents() == 0) { - n = static_cast(street->waitingAgents().size()); - double alpha{this->m_minSpeedRateo / street->capacity()}; - meanSpeed = street->maxSpeed() * n * (1. - 0.5 * alpha * (n - 1.)); - } else { - for (const auto& agentId : street->waitingAgents()) { - meanSpeed += this->m_agents.at(agentId)->speed(); - ++n; - } - for (auto const& queue : street->exitQueues()) { - for (const auto& agentId : queue) { - meanSpeed += this->m_agents.at(agentId)->speed(); - ++n; - } - } - } - const auto& node = this->m_graph.nodeSet().at(street->nodePair().second); - if (node->isIntersection()) { - auto& intersection = dynamic_cast(*node); - for (const auto& [angle, agentId] : intersection.agents()) { - const auto& agent{this->m_agents.at(agentId)}; - if (agent->streetId().has_value() && agent->streetId().value() == streetId) { - meanSpeed += agent->speed(); - ++n; - } - } - } else if (node->isRoundabout()) { - auto& roundabout = dynamic_cast(*node); - for (const auto& agentId : roundabout.agents()) { - const auto& agent{this->m_agents.at(agentId)}; - if (agent->streetId().has_value() && agent->streetId().value() == streetId) { - meanSpeed += agent->speed(); - ++n; - } - } - } - return meanSpeed / n; - } - - template - requires(std::unsigned_integral) - Measurement FirstOrderDynamics::streetMeanSpeed() const { - if (this->m_agents.size() == 0) { - return Measurement(0., 0.); - } - std::vector speeds; - speeds.reserve(this->m_graph.streetSet().size()); - for (const auto& [streetId, street] : this->m_graph.streetSet()) { - speeds.push_back(this->streetMeanSpeed(streetId)); - } - return Measurement(speeds); - } - template - requires(std::unsigned_integral) - Measurement FirstOrderDynamics::streetMeanSpeed(double threshold, - bool above) const { - if (this->m_agents.size() == 0) { - return Measurement(0., 0.); - } - std::vector speeds; - speeds.reserve(this->m_graph.streetSet().size()); - for (const auto& [streetId, street] : this->m_graph.streetSet()) { - if (above) { - if (street->density(true) > threshold) { - speeds.push_back(this->streetMeanSpeed(streetId)); - } - } else { - if (street->density(true) < threshold) { - speeds.push_back(this->streetMeanSpeed(streetId)); - } - } - } - return Measurement(speeds); - } - - /* class SecondOrderDynamics : public Dynamics { */ - /* public: */ - /* void setAgentSpeed(Size agentId); */ - /* void setSpeed(); */ - /* }; */ - - /* void SecondOrderDynamics::setAgentSpeed(Size agentId) {} */ - - /* void SecondOrderDynamics::setSpeed() {} */ - }; // namespace dsm diff --git a/src/dsm/headers/FirstOrderDynamics.hpp b/src/dsm/headers/FirstOrderDynamics.hpp new file mode 100644 index 000000000..2064d20a3 --- /dev/null +++ b/src/dsm/headers/FirstOrderDynamics.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "RoadDynamics.hpp" + +namespace dsm { + template + requires(std::unsigned_integral) + class FirstOrderDynamics : public RoadDynamics { + double m_speedFluctuationSTD; + + public: + /// @brief Construct a new First Order Dynamics object + /// @param graph, The graph representing the network + FirstOrderDynamics(Graph& graph, std::optional seed = std::nullopt) + : RoadDynamics(graph, seed), m_speedFluctuationSTD{0.} {}; + /// @brief Set the speed of an agent + /// @param agentId The id of the agent + /// @throw std::invalid_argument, If the agent is not found + void setAgentSpeed(Size agentId) override; + /// @brief Set the standard deviation of the speed fluctuation + /// @param speedFluctuationSTD The standard deviation of the speed fluctuation + /// @throw std::invalid_argument, If the standard deviation is negative + void setSpeedFluctuationSTD(double speedFluctuationSTD); + /// @brief Get the mean speed of a street in \f$m/s\f$ + /// @return double The mean speed of the street or street->maxSpeed() if the street is empty + /// @details The mean speed of a street is given by the formula: + /// \f$ v_{\text{mean}} = v_{\text{max}} \left(1 - \frac{\alpha}{2} \left( n - 1\right) \right) \f$ + /// where \f$ v_{\text{max}} \f$ is the maximum speed of the street, \f$ \alpha \f$ is the minimum speed rateo divided by the capacity + /// and \f$ n \f$ is the number of agents in the street + double streetMeanSpeed(Id streetId) const override; + /// @brief Get the mean speed of the streets in \f$m/s\f$ + /// @return Measurement The mean speed of the agents and the standard deviation + Measurement streetMeanSpeed() const override; + /// @brief Get the mean speed of the streets with density above or below a threshold in \f$m/s\f$ + /// @param threshold The density threshold to consider + /// @param above If true, the function returns the mean speed of the streets with a density above the threshold, otherwise below + /// @return Measurement The mean speed of the agents and the standard deviation + Measurement streetMeanSpeed(double threshold, bool above) const override; + }; + + template + requires(std::unsigned_integral) + void FirstOrderDynamics::setAgentSpeed(Size agentId) { + const auto& agent{this->m_agents[agentId]}; + const auto& street{this->m_graph.streetSet()[agent->streetId().value()]}; + double speed{street->maxSpeed() * + (1. - this->m_minSpeedRateo * street->density(true))}; + if (m_speedFluctuationSTD > 0.) { + std::normal_distribution speedDist{speed, speed * m_speedFluctuationSTD}; + speed = speedDist(this->m_generator); + } + speed < 0. ? agent->setSpeed(street->maxSpeed() * (1. - this->m_minSpeedRateo)) + : agent->setSpeed(speed); + } + + template + requires(std::unsigned_integral) + void FirstOrderDynamics::setSpeedFluctuationSTD(double speedFluctuationSTD) { + if (speedFluctuationSTD < 0.) { + throw std::invalid_argument( + buildLog("The speed fluctuation standard deviation must be positive.")); + } + m_speedFluctuationSTD = speedFluctuationSTD; + } + + template + requires(std::unsigned_integral) + double FirstOrderDynamics::streetMeanSpeed(Id streetId) const { + const auto& street{this->m_graph.streetSet().at(streetId)}; + if (street->nAgents() == 0) { + return street->maxSpeed(); + } + double meanSpeed{0.}; + Size n{0}; + if (street->nExitingAgents() == 0) { + n = static_cast(street->waitingAgents().size()); + double alpha{this->m_minSpeedRateo / street->capacity()}; + meanSpeed = street->maxSpeed() * n * (1. - 0.5 * alpha * (n - 1.)); + } else { + for (const auto& agentId : street->waitingAgents()) { + meanSpeed += this->m_agents.at(agentId)->speed(); + ++n; + } + for (auto const& queue : street->exitQueues()) { + for (const auto& agentId : queue) { + meanSpeed += this->m_agents.at(agentId)->speed(); + ++n; + } + } + } + const auto& node = this->m_graph.nodeSet().at(street->nodePair().second); + if (node->isIntersection()) { + auto& intersection = dynamic_cast(*node); + for (const auto& [angle, agentId] : intersection.agents()) { + const auto& agent{this->m_agents.at(agentId)}; + if (agent->streetId().has_value() && agent->streetId().value() == streetId) { + meanSpeed += agent->speed(); + ++n; + } + } + } else if (node->isRoundabout()) { + auto& roundabout = dynamic_cast(*node); + for (const auto& agentId : roundabout.agents()) { + const auto& agent{this->m_agents.at(agentId)}; + if (agent->streetId().has_value() && agent->streetId().value() == streetId) { + meanSpeed += agent->speed(); + ++n; + } + } + } + return meanSpeed / n; + } + + template + requires(std::unsigned_integral) + Measurement FirstOrderDynamics::streetMeanSpeed() const { + if (this->m_agents.size() == 0) { + return Measurement(0., 0.); + } + std::vector speeds; + speeds.reserve(this->m_graph.streetSet().size()); + for (const auto& [streetId, street] : this->m_graph.streetSet()) { + speeds.push_back(this->streetMeanSpeed(streetId)); + } + return Measurement(speeds); + } + template + requires(std::unsigned_integral) + Measurement FirstOrderDynamics::streetMeanSpeed(double threshold, + bool above) const { + if (this->m_agents.size() == 0) { + return Measurement(0., 0.); + } + std::vector speeds; + speeds.reserve(this->m_graph.streetSet().size()); + for (const auto& [streetId, street] : this->m_graph.streetSet()) { + if (above) { + if (street->density(true) > threshold) { + speeds.push_back(this->streetMeanSpeed(streetId)); + } + } else { + if (street->density(true) < threshold) { + speeds.push_back(this->streetMeanSpeed(streetId)); + } + } + } + return Measurement(speeds); + } +} // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/RoadDynamics.hpp b/src/dsm/headers/RoadDynamics.hpp new file mode 100644 index 000000000..3262a15b5 --- /dev/null +++ b/src/dsm/headers/RoadDynamics.hpp @@ -0,0 +1,658 @@ +/// @file /src/dsm/headers/RoadDynamics.hpp +/// @brief Defines the RoadDynamics class. +/// +/// @details This file contains the definition of the RoadDynamics class. +/// The RoadDynamics class represents the dynamics of the network. It is templated by the type +/// of the graph's id and the type of the graph's capacity. +/// The graph's id and capacity must be unsigned integral types. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Dynamics.hpp" +#include "Agent.hpp" +#include "DijkstraWeights.hpp" +#include "Itinerary.hpp" +#include "Graph.hpp" +#include "SparseMatrix.hpp" +#include "../utility/TypeTraits/is_agent.hpp" +#include "../utility/TypeTraits/is_itinerary.hpp" +#include "../utility/Logger.hpp" +#include "../utility/Typedef.hpp" + +namespace dsm { + /// @brief The RoadDynamics class represents the dynamics of the network. + /// @tparam Id, The type of the graph's id. It must be an unsigned integral type. + /// @tparam Size, The type of the graph's capacity. It must be an unsigned integral type. + template + requires(is_numeric_v) + class RoadDynamics : public Dynamics { + protected: + Time m_previousOptimizationTime; + double m_errorProbability; + double m_minSpeedRateo; + double m_maxFlowPercentage; + std::vector m_travelTimes; + std::unordered_map m_agentNextStreetId; + bool m_forcePriorities; + std::optional m_dataUpdatePeriod; + std::unordered_map> m_turnCounts; + std::unordered_map> m_turnMapping; + std::unordered_map m_streetTails; + + /// @brief Get the next street id + /// @param agentId The id of the agent + /// @param NodeId The id of the node + /// @param streetId The id of the incoming street + /// @return Id The id of the randomly selected next street + virtual Id m_nextStreetId(Id agentId, + Id NodeId, + std::optional streetId = std::nullopt); + /// @brief Increase the turn counts + virtual void m_increaseTurnCounts(Id streetId, double delta); + /// @brief Evolve a street + /// @param pStreet A std::unique_ptr to the street + /// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination + /// @details If possible, removes the first agent of the street's queue, putting it in the destination node. + /// If the agent is going into the destination node, it is removed from the simulation (and then reinserted if reinsert_agents is true) + void m_evolveStreet(const std::unique_ptr& pStreet, + bool reinsert_agents) override; + /// @brief If possible, removes one agent from the node, putting it on the next street. + /// @param pNode A std::unique_ptr to the node + /// @return bool True if the agent has been moved, false otherwise + bool m_evolveNode(const std::unique_ptr& pNode) override; + /// @brief Evolve the agents. + /// @details Puts all new agents on a street, if possible, decrements all delays + /// and increments all travel times. + void m_evolveAgents() override; + + public: + /// @brief Construct a new RoadDynamics object + /// @param graph The graph representing the network + RoadDynamics(Graph& graph, std::optional seed); + + /// @brief Set the minim speed rateo, i.e. the minim speed with respect to the speed limit + /// @param minSpeedRateo The minim speed rateo + /// @throw std::invalid_argument If the minim speed rateo is not between 0 and 1 + void setMinSpeedRateo(double minSpeedRateo); + /// @brief Set the error probability + /// @param errorProbability The error probability + /// @throw std::invalid_argument If the error probability is not between 0 and 1 + void setErrorProbability(double errorProbability); + /// @brief Set the maximum flow percentage + /// @param maxFlowPercentage The maximum flow percentage + /// @details The maximum flow percentage is the percentage of the maximum flow that a street can transmit. Default is 1 (100%). + /// @throw std::invalid_argument If the maximum flow percentage is not between 0 and 1 + void setMaxFlowPercentage(double maxFlowPercentage); + /// @brief Set the force priorities flag + /// @param forcePriorities The flag + /// @details If true, if an agent cannot move to the next street, the whole node is skipped + void setForcePriorities(bool forcePriorities) { m_forcePriorities = forcePriorities; } + /// @brief Set the data update period. + /// @param dataUpdatePeriod delay_t, The period + /// @details Some data, i.e. the street queue lengths, are stored only after a fixed amount of time which is represented by this variable. + void setDataUpdatePeriod(delay_t dataUpdatePeriod) { + m_dataUpdatePeriod = dataUpdatePeriod; + } + + /// @brief Evolve the simulation + /// @details Evolve the simulation by moving the agents and updating the travel times. + /// In particular: + /// - Move the first agent of each street queue, if possible, putting it in the next node + /// - Move the agents from each node, if possible, putting them in the next street and giving them a speed. + /// If the error probability is not zero, the agents can move to a random street. + /// If the agent is in the destination node, it is removed from the simulation (and then reinserted if reinsert_agents is true) + /// - Cycle over agents and update their times + /// @param reinsert_agents If true, the agents are reinserted in the simulation after they reach their destination + void evolve(bool reinsert_agents = false) override; + /// @brief Optimize the traffic lights by changing the green and red times + /// @param threshold double, The percentage of the mean capacity of the streets used as threshold for the delta between the two tails. + /// @param densityTolerance double, The algorithm will consider all streets with density up to densityTolerance*meanDensity + /// @param optimizationType TrafficLightOptimization, The type of optimization. Default is DOUBLE_TAIL + /// @details The function cycles over the traffic lights and, if the difference between the two tails is greater than + /// 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. + /// The optimizationType parameter can be set to SINGLE_TAIL to use an algorith which looks only at the incoming street tails or to DOUBLE_TAIL to consider both incoming and outgoing street tails. + void optimizeTrafficLights(double const threshold = 0., + double const densityTolerance = 0., + TrafficLightOptimization optimizationType = + TrafficLightOptimization::DOUBLE_TAIL); + /// @brief Get the mean travel time of the agents in \f$s\f$ + /// @param clearData If true, the travel times are cleared after the computation + /// @return Measurement The mean travel time of the agents and the standard + Measurement meanTravelTime(bool clearData = false); + /// @brief Get the turn counts of the agents + /// @return const std::array& The turn counts + /// @details The array contains the counts of left (0), straight (1), right (2) and U (3) turns + const std::unordered_map>& turnCounts() const { + return m_turnCounts; + } + /// @brief Get the turn probabilities of the agents + /// @return std::array The turn probabilities + /// @details The array contains the probabilities of left (0), straight (1), right (2) and U (3) turns + std::unordered_map> turnProbabilities(bool reset = true); + + std::unordered_map> turnMapping() const { + return m_turnMapping; + } + }; + + template + requires(is_numeric_v) + RoadDynamics::RoadDynamics(Graph& graph, std::optional seed) + : Dynamics(graph, seed), + m_previousOptimizationTime{0}, + m_errorProbability{0.}, + m_minSpeedRateo{0.}, + m_maxFlowPercentage{1.}, + m_forcePriorities{false} { + for (const auto& [streetId, street] : this->m_graph.streetSet()) { + m_streetTails.emplace(streetId, 0); + m_turnCounts.emplace(streetId, std::array{0, 0, 0, 0}); + // fill turn mapping as [streetId, [left street Id, straight street Id, right street Id, U self street Id]] + m_turnMapping.emplace(streetId, std::array{-1, -1, -1, -1}); + // Turn mappings + const auto& srcNodeId = street->nodePair().second; + for (const auto& [ss, _] : this->m_graph.adjMatrix().getRow(srcNodeId, true)) { + const auto& delta = street->angle() - this->m_graph.streetSet()[ss]->angle(); + if (std::abs(delta) < std::numbers::pi) { + if (delta < 0.) { + m_turnMapping[streetId][dsm::Direction::RIGHT] = ss; + ; // right + } else if (delta > 0.) { + m_turnMapping[streetId][dsm::Direction::LEFT] = ss; // left + } else { + m_turnMapping[streetId][dsm::Direction::STRAIGHT] = ss; // straight + } + } else { + m_turnMapping[streetId][dsm::Direction::UTURN] = ss; // U + } + } + } + } + + template + requires(is_numeric_v) + Id RoadDynamics::m_nextStreetId(Id agentId, + Id nodeId, + std::optional streetId) { + auto possibleMoves = this->m_graph.adjMatrix().getRow(nodeId, true); + std::uniform_real_distribution uniformDist{0., 1.}; + if (this->m_itineraries.size() > 0 && + uniformDist(this->m_generator) > m_errorProbability) { + const auto& it = this->m_itineraries[this->m_agents[agentId]->itineraryId()]; + if (it->destination() != nodeId) { + possibleMoves = it->path().getRow(nodeId, true); + } + } + assert(possibleMoves.size() > 0); + std::uniform_int_distribution moveDist{ + 0, static_cast(possibleMoves.size() - 1)}; + uint8_t p{0}; + auto iterator = possibleMoves.begin(); + // while loop to avoid U turns in non-roundabout junctions + do { + p = moveDist(this->m_generator); + iterator = possibleMoves.begin(); + std::advance(iterator, p); + } while (!this->m_graph.nodeSet().at(nodeId)->isRoundabout() and + streetId.has_value() and + (this->m_graph.streetSet()[iterator->first]->nodePair().second == + this->m_graph.streetSet()[streetId.value()]->nodePair().first) and + (possibleMoves.size() > 1)); + return iterator->first; + } + + template + requires(is_numeric_v) + void RoadDynamics::m_increaseTurnCounts(Id streetId, double delta) { + if (std::abs(delta) < std::numbers::pi) { + if (delta < 0.) { + ++m_turnCounts[streetId][0]; // right + } else if (delta > 0.) { + ++m_turnCounts[streetId][2]; // left + } else { + ++m_turnCounts[streetId][1]; // straight + } + } else { + ++m_turnCounts[streetId][3]; // U + } + } + + template + requires(is_numeric_v) + void RoadDynamics::m_evolveStreet(const std::unique_ptr& pStreet, + bool reinsert_agents) { + auto const nLanes = pStreet->nLanes(); + std::uniform_real_distribution uniformDist{0., 1.}; + for (auto queueIndex = 0; queueIndex < nLanes; ++queueIndex) { + if (uniformDist(this->m_generator) > m_maxFlowPercentage || + pStreet->queue(queueIndex).empty()) { + continue; + } + const auto agentId{pStreet->queue(queueIndex).front()}; + if (this->m_agents[agentId]->delay() > 0) { + continue; + } + this->m_agents[agentId]->setSpeed(0.); + const auto& destinationNode{this->m_graph.nodeSet()[pStreet->nodePair().second]}; + if (destinationNode->isFull()) { + continue; + } + if (destinationNode->isTrafficLight()) { + auto& tl = dynamic_cast(*destinationNode); + auto const direction{pStreet->laneMapping().at(queueIndex)}; + if (!tl.isGreen(pStreet->id(), direction)) { + continue; + } + } + if (destinationNode->id() == + this->m_itineraries[this->m_agents[agentId]->itineraryId()]->destination()) { + pStreet->dequeue(queueIndex); + m_travelTimes.push_back(this->m_agents[agentId]->time()); + if (reinsert_agents) { + // take last agent id in map + Agent newAgent{static_cast(this->m_agents.rbegin()->first + 1), + this->m_agents[agentId]->itineraryId(), + this->m_agents[agentId]->srcNodeId().value()}; + if (this->m_agents[agentId]->srcNodeId().has_value()) { + newAgent.setSourceNodeId(this->m_agents[agentId]->srcNodeId().value()); + } + this->removeAgent(agentId); + this->addAgent(newAgent); + } else { + this->removeAgent(agentId); + } + continue; + } + auto const& nextStreet{this->m_graph.streetSet()[m_agentNextStreetId[agentId]]}; + if (nextStreet->isFull()) { + continue; + } + pStreet->dequeue(queueIndex); + assert(destinationNode->id() == nextStreet->nodePair().first); + if (destinationNode->isIntersection()) { + auto& intersection = dynamic_cast(*destinationNode); + auto const delta{nextStreet->deltaAngle(pStreet->angle())}; + m_increaseTurnCounts(pStreet->id(), delta); + intersection.addAgent(delta, agentId); + } else if (destinationNode->isRoundabout()) { + auto& roundabout = dynamic_cast(*destinationNode); + roundabout.enqueue(agentId); + } + } + } + + template + requires(is_numeric_v) + bool RoadDynamics::m_evolveNode(const std::unique_ptr& pNode) { + if (pNode->isIntersection()) { + auto& intersection = dynamic_cast(*pNode); + if (intersection.agents().empty()) { + return false; + } + for (auto const [angle, agentId] : intersection.agents()) { + auto const& nextStreet{this->m_graph.streetSet()[m_agentNextStreetId[agentId]]}; + if (nextStreet->isFull()) { + if (m_forcePriorities) { + return false; + } + continue; + } + intersection.removeAgent(agentId); + this->m_agents[agentId]->setStreetId(nextStreet->id()); + this->setAgentSpeed(agentId); + this->m_agents[agentId]->incrementDelay( + std::ceil(nextStreet->length() / this->m_agents[agentId]->speed())); + nextStreet->addAgent(agentId); + m_agentNextStreetId.erase(agentId); + return true; + } + return false; + } else if (pNode->isRoundabout()) { + auto& roundabout = dynamic_cast(*pNode); + if (roundabout.agents().empty()) { + return false; + } + auto const agentId{roundabout.agents().front()}; + auto const& nextStreet{this->m_graph.streetSet()[m_agentNextStreetId[agentId]]}; + if (!(nextStreet->isFull())) { + if (this->m_agents[agentId]->streetId().has_value()) { + const auto streetId = this->m_agents[agentId]->streetId().value(); + auto delta = nextStreet->angle() - this->m_graph.streetSet()[streetId]->angle(); + if (delta > std::numbers::pi) { + delta -= 2 * std::numbers::pi; + } else if (delta < -std::numbers::pi) { + delta += 2 * std::numbers::pi; + } + m_increaseTurnCounts(streetId, delta); + } + roundabout.dequeue(); + this->m_agents[agentId]->setStreetId(nextStreet->id()); + this->setAgentSpeed(agentId); + this->m_agents[agentId]->incrementDelay( + std::ceil(nextStreet->length() / this->m_agents[agentId]->speed())); + nextStreet->addAgent(agentId); + m_agentNextStreetId.erase(agentId); + } else { + return false; + } + } + return true; + } + + template + requires(is_numeric_v) + void RoadDynamics::m_evolveAgents() { + for (const auto& [agentId, agent] : this->m_agents) { + if (agent->delay() > 0) { + const auto& street{this->m_graph.streetSet()[agent->streetId().value()]}; + if (agent->delay() > 1) { + agent->incrementDistance(); + } else { + double distance{std::fmod(street->length(), agent->speed())}; + if (distance < std::numeric_limits::epsilon()) { + agent->incrementDistance(); + } else { + agent->incrementDistance(distance); + } + } + agent->decrementDelay(); + if (agent->delay() == 0) { + auto const nLanes = street->nLanes(); + if (this->m_itineraries[agent->itineraryId()]->destination() == + street->nodePair().second) { + std::uniform_int_distribution laneDist{ + 0, static_cast(nLanes - 1)}; + street->enqueue(agentId, laneDist(this->m_generator)); + } else { + auto const nextStreetId = + this->m_nextStreetId(agentId, street->nodePair().second, street->id()); + auto const& pNextStreet{this->m_graph.streetSet()[nextStreetId]}; + m_agentNextStreetId.emplace(agentId, nextStreetId); + if (nLanes == 1) { + street->enqueue(agentId, 0); + } else { + auto const deltaAngle{pNextStreet->deltaAngle(street->angle())}; + if (std::abs(deltaAngle) < std::numbers::pi) { + // Lanes are counted as 0 is the far right lane + if (deltaAngle < 0.) { // Right + street->enqueue(agentId, 0); // Always the first lane + } else if (deltaAngle > 0.) { // Left + street->enqueue(agentId, nLanes - 1); // Always the last lane + } else { // Straight + std::uniform_int_distribution laneDist{ + 0, static_cast(nLanes - 2)}; + street->enqueue(agentId, laneDist(this->m_generator)); + } + } else { // U turn + street->enqueue(agentId, nLanes - 1); // Always the last lane + } + } + } + } + } else if (!agent->streetId().has_value() && + !m_agentNextStreetId.contains(agentId)) { + assert(agent->srcNodeId().has_value()); + const auto& srcNode{this->m_graph.nodeSet()[agent->srcNodeId().value()]}; + if (srcNode->isFull()) { + continue; + } + const auto& nextStreet{ + this->m_graph.streetSet()[this->m_nextStreetId(agentId, srcNode->id())]}; + if (nextStreet->isFull()) { + continue; + } + assert(srcNode->id() == nextStreet->nodePair().first); + if (srcNode->isIntersection()) { + auto& intersection = dynamic_cast(*srcNode); + intersection.addAgent(0., agentId); + } else if (srcNode->isRoundabout()) { + auto& roundabout = dynamic_cast(*srcNode); + roundabout.enqueue(agentId); + } + m_agentNextStreetId.emplace(agentId, nextStreet->id()); + } else if (agent->delay() == 0) { + agent->setSpeed(0.); + } + agent->incrementTime(); + } + } + + template + requires(is_numeric_v) + void RoadDynamics::setMinSpeedRateo(double minSpeedRateo) { + if (minSpeedRateo < 0. || minSpeedRateo > 1.) { + throw std::invalid_argument(buildLog(std::format( + "The minimum speed rateo ({}) must be between 0 and 1", minSpeedRateo))); + } + m_minSpeedRateo = minSpeedRateo; + } + + template + requires(is_numeric_v) + void RoadDynamics::setErrorProbability(double errorProbability) { + if (errorProbability < 0. || errorProbability > 1.) { + throw std::invalid_argument(buildLog(std::format( + "The error probability ({}) must be between 0 and 1", errorProbability))); + } + m_errorProbability = errorProbability; + } + + template + requires(is_numeric_v) + void RoadDynamics::setMaxFlowPercentage(double maxFlowPercentage) { + if (maxFlowPercentage < 0. || maxFlowPercentage > 1.) { + throw std::invalid_argument( + buildLog(std::format("The maximum flow percentage ({}) must be between 0 and 1", + maxFlowPercentage))); + } + m_maxFlowPercentage = maxFlowPercentage; + } + + template + requires(is_numeric_v) + void RoadDynamics::evolve(bool reinsert_agents) { + // move the first agent of each street queue, if possible, putting it in the next node + bool const bUpdateData = + m_dataUpdatePeriod.has_value() && this->m_time % m_dataUpdatePeriod.value() == 0; + for (const auto& [streetId, pStreet] : this->m_graph.streetSet()) { + if (bUpdateData) { + m_streetTails[streetId] += pStreet->nExitingAgents(); + } + for (auto i = 0; i < pStreet->transportCapacity(); ++i) { + this->m_evolveStreet(pStreet, reinsert_agents); + } + } + // Move transport capacity agents from each node + for (const auto& [nodeId, pNode] : this->m_graph.nodeSet()) { + for (auto i = 0; i < pNode->transportCapacity(); ++i) { + if (!this->m_evolveNode(pNode)) { + break; + } + } + if (pNode->isTrafficLight()) { + auto& tl = dynamic_cast(*pNode); + ++tl; // Increment the counter + } + } + // cycle over agents and update their times + this->m_evolveAgents(); + // increment time simulation + ++this->m_time; + } + + template + requires(is_numeric_v) + void RoadDynamics::optimizeTrafficLights( + double const threshold, + double const densityTolerance, + TrafficLightOptimization const optimizationType) { + if (threshold < 0 || threshold > 1) { + throw std::invalid_argument( + buildLog(std::format("The threshold parameter is a percentage and must be " + "bounded between 0-1. Inserted value: {}", + threshold))); + } + if (densityTolerance < 0 || densityTolerance > 1) { + throw std::invalid_argument(buildLog( + std::format("The densityTolerance parameter is a percentage and must be " + "bounded between 0-1. Inserted value: {}", + densityTolerance))); + } + auto const meanDensityGlob = this->streetMeanDensity().mean; // Measurement + for (const auto& [nodeId, pNode] : this->m_graph.nodeSet()) { + if (!pNode->isTrafficLight()) { + continue; + } + auto& tl = dynamic_cast(*pNode); + const auto& streetPriorities = tl.streetPriorities(); + Size greenSum{0}, greenQueue{0}; + Size redSum{0}, redQueue{0}; + for (const auto& [streetId, _] : this->m_graph.adjMatrix().getCol(nodeId, true)) { + if (streetPriorities.contains(streetId)) { + greenSum += m_streetTails[streetId]; + for (auto const& queue : this->m_graph.streetSet()[streetId]->exitQueues()) { + greenQueue += queue.size(); + } + } else { + redSum += m_streetTails[streetId]; + for (auto const& queue : this->m_graph.streetSet()[streetId]->exitQueues()) { + redQueue += queue.size(); + } + } + } + const auto nCycles = + static_cast(this->m_time - m_previousOptimizationTime) / + m_dataUpdatePeriod.value(); + const delay_t delta = + std::floor(std::fabs(static_cast(greenQueue - redQueue)) / nCycles); + // std::cout << std::format("GreenSum: {}, RedSum: {}, Delta: {}, nCycles: {}\n", + // greenQueue, redQueue, delta, nCycles); + if (delta == 0) { + continue; + } + const Size smallest = std::min(greenSum, redSum); + // std::cout << std::format("GreenSum: {}, RedSum: {}, Smallest: {}\n", greenSum, redSum, smallest); + // std::cout << std::format("Diff: {}, Threshold * Smallest: {}\n", std::abs(static_cast(greenSum - redSum)), threshold * smallest); + if (std::abs(static_cast(greenSum - redSum)) < threshold * smallest) { + tl.resetCycles(); + continue; + } + auto const greenTime = tl.maxGreenTime(true); + auto const redTime = tl.maxGreenTime(false); + if (optimizationType == TrafficLightOptimization::SINGLE_TAIL) { + if ((greenSum > redSum) && !(greenTime > redTime) && (redTime > delta) && + (greenQueue > redQueue)) { + tl.increaseGreenTimes(delta); + } else if ((redSum > greenSum) && !(redTime > greenTime) && (greenTime > delta) && + (redQueue > greenQueue)) { + tl.decreaseGreenTimes(delta); + } else { + tl.resetCycles(); + } + } else if (optimizationType == TrafficLightOptimization::DOUBLE_TAIL) { + // If the difference is not less than the threshold + // - 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 + // - If the previous check fails, do nothing + double meanDensity_streets{0.}; + { + // Store the ids of outgoing streets + const auto& row{this->m_graph.adjMatrix().getRow(nodeId, true)}; + for (const auto& [streetId, _] : row) { + meanDensity_streets += this->m_graph.streetSet()[streetId]->density(); + } + // Take the mean density of the outgoing streets + const auto nStreets = row.size(); + if (nStreets > 1) { + meanDensity_streets /= nStreets; + } + } + auto const ratio = meanDensityGlob / meanDensity_streets; + // densityTolerance represents the max border we want to consider + auto const dyn_thresh = std::tanh(ratio) * densityTolerance; + if (meanDensityGlob * (1. + dyn_thresh) > meanDensity_streets) { + if (meanDensityGlob > meanDensity_streets) { + // Smaller than max density + if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { + tl.decreaseGreenTimes(delta); + } else if (!(redTime < greenTime) && (greenSum > redSum) && + (redTime > delta)) { + tl.increaseGreenTimes(delta); + } else { + tl.resetCycles(); + } + } else { + // Greater than max density + if (!(redTime > greenTime) && (redSum > greenSum) && (greenTime > delta)) { + tl.decreaseGreenTimes(delta * dyn_thresh); + } else if (!(redTime < greenTime) && (greenSum > redSum) && + (redTime > delta)) { + tl.increaseGreenTimes(delta * dyn_thresh); + } else { + tl.resetCycles(); + } + } + } + } + } + // Cleaning variables + for (auto& [id, element] : m_streetTails) { + element = 0; + } + m_previousOptimizationTime = this->m_time; + } + + template + requires(is_numeric_v) + Measurement RoadDynamics::meanTravelTime(bool clearData) { + std::vector travelTimes; + if (!m_travelTimes.empty()) { + if (clearData) { + std::swap(travelTimes, m_travelTimes); + } else { + travelTimes.reserve(m_travelTimes.size()); + for (const auto& time : m_travelTimes) { + travelTimes.push_back(time); + } + } + } + return Measurement(travelTimes); + } + + template + requires(is_numeric_v) + std::unordered_map> RoadDynamics::turnProbabilities( + bool reset) { + std::unordered_map> res; + for (auto& [streetId, counts] : m_turnCounts) { + std::array probabilities{0., 0., 0., 0.}; + const auto sum{std::accumulate(counts.cbegin(), counts.cend(), 0.)}; + if (sum != 0) { + for (auto i{0}; i < counts.size(); ++i) { + probabilities[i] = counts[i] / sum; + } + } + res.emplace(streetId, probabilities); + } + if (reset) { + for (auto& [streetId, counts] : m_turnCounts) { + std::fill(counts.begin(), counts.end(), 0); + } + } + return res; + } + +}; // namespace dsm diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index f1413462b..07e554da4 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -1,6 +1,6 @@ #include -#include "Dynamics.hpp" +#include "FirstOrderDynamics.hpp" #include "Graph.hpp" #include "SparseMatrix.hpp" #include "Street.hpp" @@ -54,7 +54,7 @@ TEST_CASE("Dynamics") { graph.importMatrix("./data/matrix.dsm"); graph.buildAdj(); WHEN("A dynamics object is created") { - Dynamics dynamics(graph); + Dynamics dynamics{graph, 69}; THEN("The node and the street sets are the same") { CHECK_EQ(dynamics.graph().nNodes(), 3); CHECK_EQ(dynamics.graph().nEdges(), 4); @@ -72,7 +72,7 @@ TEST_CASE("Dynamics") { } WHEN("We transform a node into a traffic light and create the dynamics") { auto& tl = graph.makeTrafficLight(0, 2); - Dynamics dynamics(graph); + Dynamics dynamics{graph, 69}; THEN("The node is a traffic light") { CHECK(dynamics.graph().nodeSet().at(0)->isTrafficLight()); CHECK_EQ(tl.cycleTime(), 2); @@ -80,14 +80,14 @@ TEST_CASE("Dynamics") { } WHEN("We transform a node into a roundabout and create the dynamics") { graph.makeRoundabout(0); - Dynamics dynamics(graph); + Dynamics dynamics{graph, 69}; THEN("The node is a roundabout") { CHECK(dynamics.graph().nodeSet().at(0)->isRoundabout()); } } WHEN("We transorm a street into a spire and create the dynamcis") { graph.makeSpireStreet(8); - Dynamics dynamics(graph); + Dynamics dynamics{graph, 69}; THEN("The street is a spire") { CHECK(dynamics.graph().streetSet().at(8)->isSpire()); } @@ -98,7 +98,7 @@ TEST_CASE("Dynamics") { GIVEN("A dynamics object and a destination node") { auto graph = Graph{}; graph.importMatrix("./data/matrix.dat"); - Dynamics dynamics{graph}; + Dynamics dynamics{graph, 69}; WHEN("We add a span of destination nodes") { std::array nodes{0, 1, 2}; dynamics.setDestinationNodes(nodes); @@ -122,7 +122,7 @@ TEST_CASE("Dynamics") { GIVEN("A dynamics object, a source node and a destination node") { auto graph = Graph{}; graph.importMatrix("./data/matrix.dsm"); - Dynamics dynamics{graph}; + Dynamics dynamics{graph, 69}; dynamics.addItinerary(Itinerary{2, 2}); WHEN("We add the agent") { dynamics.addAgent(0, 2); @@ -150,7 +150,7 @@ TEST_CASE("Dynamics") { GIVEN("A dynamics object and an itinerary") { auto graph = Graph{}; graph.importMatrix("./data/matrix.dsm"); - Dynamics dynamics(graph); + Dynamics dynamics{graph, 69}; WHEN("We add agents without adding itineraries") { THEN("An exception is thrown") { CHECK_THROWS_AS(dynamics.addAgentsUniformly(1), std::invalid_argument); @@ -174,8 +174,7 @@ TEST_CASE("Dynamics") { GIVEN("A dynamics object and many itineraries") { auto graph = Graph{}; graph.importMatrix("./data/matrix.dsm"); - Dynamics dynamics(graph); - dynamics.setSeed(69); + Dynamics dynamics{graph, 69}; Itinerary Itinerary1{0, 2}, Itinerary2{1, 1}; dynamics.addItinerary(Itinerary1); dynamics.addItinerary(Itinerary2); @@ -230,8 +229,7 @@ TEST_CASE("Dynamics") { GIVEN("A graph object") { auto graph = Graph{}; graph.importMatrix("./data/matrix.dat"); - Dynamics dynamics{graph}; - dynamics.setSeed(69); + Dynamics dynamics{graph, 69}; WHEN("We add one agent for existing itinerary") { std::unordered_map src{{0, 1.}}; std::unordered_map dst{{2, 1.}}; @@ -285,7 +283,7 @@ TEST_CASE("Dynamics") { GIVEN("A dynamics object and one itinerary") { auto graph = Graph{}; graph.importMatrix("./data/matrix.dsm"); - Dynamics dynamics{graph}; + Dynamics dynamics{graph, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); WHEN("We add an agent with itinerary 1") { @@ -317,7 +315,7 @@ TEST_CASE("Dynamics") { Graph graph2; graph2.addStreets(s); graph2.buildAdj(); - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 1}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -340,7 +338,7 @@ TEST_CASE("Dynamics") { Graph graph2; graph2.addStreets(s1); graph2.buildAdj(); - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; WHEN("We add a topologically impossible itinerary") { Itinerary itinerary{0, 0}; dynamics.addItinerary(itinerary); @@ -356,7 +354,7 @@ TEST_CASE("Dynamics") { Graph graph2; graph2.addStreets(s1, s2, s3); graph2.buildAdj(); - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 2}; WHEN("We add an itinerary and update the paths") { dynamics.addItinerary(itinerary); @@ -391,7 +389,7 @@ TEST_CASE("Dynamics") { Itinerary it2{1, 118}; Itinerary it3{2, 118}; Itinerary it4{3, 118}; - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; dynamics.addItinerary(it1); dynamics.addItinerary(it2); dynamics.addItinerary(it3); @@ -416,7 +414,7 @@ TEST_CASE("Dynamics") { Graph graph; graph.addStreets(s1, s2, s3, s4); graph.buildAdj(); - Dynamics dynamics{graph}; + Dynamics dynamics{graph, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); WHEN("We update the paths") { @@ -452,8 +450,7 @@ TEST_CASE("Dynamics") { Graph graph; graph.addStreets(s1, s2, s3); graph.buildAdj(); - Dynamics dynamics{graph}; - dynamics.setSeed(69); + Dynamics dynamics{graph, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -487,8 +484,7 @@ TEST_CASE("Dynamics") { Graph graph2; graph2.addStreets(s1, s2); graph2.buildAdj(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 1}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -515,8 +511,7 @@ TEST_CASE("Dynamics") { Graph graph2; graph2.addStreets(s1, s2); graph2.buildAdj(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 1}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -559,8 +554,7 @@ TEST_CASE("Dynamics") { graph2.addNode(std::make_unique(tl)); graph2.addStreets(s1, s2, s3, s4); graph2.buildAdj(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -625,8 +619,7 @@ TEST_CASE("Dynamics") { nodes.at(4)->setCoords({1., 0.}); graph2.buildStreetAngles(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; std::vector destinationNodes{0, 2, 3, 4}; dynamics.setDestinationNodes(destinationNodes); @@ -697,8 +690,7 @@ TEST_CASE("Dynamics") { nodes.at(4)->setCoords({1., 0.}); graph2.buildStreetAngles(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; std::vector destinationNodes{0, 2, 3, 4}; dynamics.setDestinationNodes(destinationNodes); @@ -754,7 +746,7 @@ TEST_CASE("Dynamics") { tl.setCycle(11, dsm::Direction::ANY, {4, 0}); tl.setComplementaryCycle(16, 11); tl.setComplementaryCycle(21, 11); - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; std::vector destinationNodes{0, 2, 3, 4}; dynamics.setDestinationNodes(destinationNodes); dynamics.addAgents(0, 7, 2); @@ -818,8 +810,7 @@ TEST_CASE("Dynamics") { graph2.buildAdj(); auto& rb = graph2.makeRoundabout(1); graph2.adjustNodeCapacities(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 2}; Itinerary itinerary2{1, 0}; dynamics.addItinerary(itinerary); @@ -857,8 +848,7 @@ TEST_CASE("Dynamics") { Graph graph2; graph2.addStreets(s1, s2); graph2.buildAdj(); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -891,9 +881,8 @@ TEST_CASE("Dynamics") { node->setCapacity(4); node->setTransportCapacity(4); } - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; dynamics.setMinSpeedRateo(0.5); - dynamics.setSeed(69); Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -959,9 +948,8 @@ TEST_CASE("Dynamics") { graph2.addStreet(sOC); graph2.addStreet(sOD); graph2.buildAdj(); - Dynamics dynamics{graph2}; + Dynamics dynamics{graph2, 69}; dynamics.graph().nodeSet().at(0)->setCapacity(3); - dynamics.setSeed(69); Itinerary itinerary{0, 2}; Itinerary itinerary2{1, 1}; dynamics.addItinerary(itinerary); @@ -1023,8 +1011,7 @@ TEST_CASE("Dynamics") { graph2.addStreet(s); graph2.buildAdj(); graph2.makeSpireStreet(1); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -1056,8 +1043,7 @@ TEST_CASE("Dynamics") { graph2.addStreet(s); graph2.buildAdj(); graph2.makeSpireStreet(1); - Dynamics dynamics{graph2}; - dynamics.setSeed(69); + Dynamics dynamics{graph2, 69}; Itinerary itinerary{0, 2}; dynamics.addItinerary(itinerary); dynamics.updatePaths();