diff --git a/examples/stalingrado.cpp b/examples/stalingrado.cpp index 53fa5dae7..c0324dc75 100644 --- a/examples/stalingrado.cpp +++ b/examples/stalingrado.cpp @@ -49,7 +49,7 @@ int main() { Graph graph; // Street(StreetId, Capacity, Length, vMax, (from, to)) - Street::setMeanVehicleLength(8.); + dsm::Road::setMeanVehicleLength(8.); Street s01{1, std::make_pair(0, 1), 2281.}; Street s12{7, std::make_pair(1, 2), 118.}; Street s23{13, std::make_pair(2, 3), 222.}; @@ -74,7 +74,8 @@ int main() { graph.addStreets(s01, s12, s23, s34); graph.buildAdj(); graph.adjustNodeCapacities(); - auto& spire = graph.makeSpireStreet(19); + graph.makeSpireStreet(19); + auto& spire = dynamic_cast(*graph.streetSet().at(19)); std::cout << "Intersections: " << graph.nNodes() << '\n'; std::cout << "Streets: " << graph.nEdges() << '\n'; diff --git a/src/dsm/dsm.hpp b/src/dsm/dsm.hpp index aa3f64478..5808a34ae 100644 --- a/src/dsm/dsm.hpp +++ b/src/dsm/dsm.hpp @@ -6,7 +6,7 @@ static constexpr uint8_t DSM_VERSION_MAJOR = 2; static constexpr uint8_t DSM_VERSION_MINOR = 3; -static constexpr uint8_t DSM_VERSION_PATCH = 1; +static constexpr uint8_t DSM_VERSION_PATCH = 2; static auto const DSM_VERSION = std::format("{}.{}.{}", DSM_VERSION_MAJOR, DSM_VERSION_MINOR, DSM_VERSION_PATCH); diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index 7b43a1eca..95e276692 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -428,7 +428,7 @@ namespace dsm { return 0.; } double speed{0.}; - for (auto const& agentId : pStreet->waitingAgents()) { + for (auto const& agentId : pStreet->movingAgents()) { speed += m_agents.at(agentId)->speed(); } return speed / nAgents; diff --git a/src/dsm/headers/Edge.cpp b/src/dsm/headers/Edge.cpp index ebe7e11b6..53fed38a1 100644 --- a/src/dsm/headers/Edge.cpp +++ b/src/dsm/headers/Edge.cpp @@ -1,15 +1,23 @@ #include "Edge.hpp" #include "../utility/Logger.hpp" -#include +#include +#include #include +#include +#include namespace dsm { - Edge::Edge(Id id, std::pair nodePair, int capacity, int transportCapacity) + Edge::Edge(Id id, + std::pair nodePair, + int capacity, + int transportCapacity, + double angle) : m_id(id), m_nodePair(nodePair), m_capacity{capacity}, - m_transportCapacity{transportCapacity} { + m_transportCapacity{transportCapacity}, + m_angle{angle} { if (capacity < 1) { throw std::invalid_argument( buildLog(std::format("Edge capacity ({}) must be greater than 0.", capacity))); @@ -18,6 +26,10 @@ namespace dsm { throw std::invalid_argument(buildLog(std::format( "Edge transport capacity ({}) must be greater than 0.", transportCapacity))); } + if (std::abs(angle) > 2 * std::numbers::pi) { + throw std::invalid_argument(buildLog( + std::format("Edge angle ({}) must be in the range [-2pi, 2pi].", angle))); + } } void Edge::setCapacity(int capacity) { @@ -35,10 +47,34 @@ namespace dsm { m_transportCapacity = capacity; } + void Edge::setAngle(std::pair srcNodeCoordinates, + std::pair dstNodeCoordinates) { + // N.B.: lat, lon <==> y, x + double const dy{dstNodeCoordinates.first - srcNodeCoordinates.first}; + double const dx{dstNodeCoordinates.second - srcNodeCoordinates.second}; + double angle{std::atan2(dy, dx)}; + if (angle < 0.) { + angle += 2 * std::numbers::pi; + } + assert(!(std::abs(angle) > 2 * std::numbers::pi)); + m_angle = angle; + } + Id Edge::id() const { return m_id; } Id Edge::u() const { return m_nodePair.first; } Id Edge::v() const { return m_nodePair.second; } - std::pair Edge::nodePair() const { return m_nodePair; } + std::pair const& Edge::nodePair() const { return m_nodePair; } int Edge::capacity() const { return m_capacity; } int Edge::transportCapacity() const { return m_transportCapacity; } + double Edge::angle() const { return m_angle; } + + double Edge::deltaAngle(double const previousEdgeAngle) const { + double deltaAngle{this->m_angle - previousEdgeAngle}; + if (deltaAngle > std::numbers::pi) { + deltaAngle -= 2 * std::numbers::pi; + } else if (deltaAngle < -std::numbers::pi) { + deltaAngle += 2 * std::numbers::pi; + } + return deltaAngle; + } }; // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/Edge.hpp b/src/dsm/headers/Edge.hpp index 9cd54b11f..c670e88ab 100644 --- a/src/dsm/headers/Edge.hpp +++ b/src/dsm/headers/Edge.hpp @@ -10,20 +10,53 @@ namespace dsm { std::pair m_nodePair; int m_capacity; int m_transportCapacity; + double m_angle; public: - Edge(Id id, std::pair nodePair, int capacity = 1, int transportCapacity = 1); + /// @brief Construct a new Edge object + /// @param id The edge's id + /// @param nodePair The edge's node pair (u, v) with the edge u -> v + /// @param capacity The edge's capacity, in number of agents, i.e. the maximum number of agents that can be on the + /// edge at the same time. Default is 1. + /// @param transportCapacity The edge's transport capacity, in number of agents, i.e. the maximum number of agents + /// that can be emitted by the same time. Default is 1. + /// @param angle The edge's angle, in radians, between the source and destination nodes + Edge(Id id, + std::pair nodePair, + int capacity = 1, + int transportCapacity = 1, + double angle = 0.0); void setCapacity(int capacity); void setTransportCapacity(int capacity); + void setAngle(std::pair srcNodeCoordinates, + std::pair dstNodeCoordinates); + /// @brief Get the edge's id + /// @return Id The edge's id Id id() const; + /// @brief Get the edge's source node id + /// @return Id The edge's source node id Id u() const; + /// @brief Get the edge's destination node id + /// @return Id The edge's destination node id Id v() const; - std::pair nodePair() const; + /// @brief Get the edge's node pair + /// @return std::pair The edge's node pair, where the first element is the source node id and the second + /// element is the destination node id. The pair is (u, v) with the edge u -> v. + std::pair const& nodePair() const; + /// @brief Get the edge's capacity, in number of agents + /// @return int The edge's capacity, in number of agents int capacity() const; + /// @brief Get the edge's transport capacity, in number of agents + /// @return int The edge's transport capacity, in number of agents int transportCapacity() const; + /// @brief Get the edge's angle, in radians, between the source and destination nodes + /// @return double The edge's angle, in radians + double angle() const; virtual bool isFull() const = 0; + + double deltaAngle(double const previousEdgeAngle) const; }; }; // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/FirstOrderDynamics.cpp b/src/dsm/headers/FirstOrderDynamics.cpp index 1f22dc899..8219ff076 100644 --- a/src/dsm/headers/FirstOrderDynamics.cpp +++ b/src/dsm/headers/FirstOrderDynamics.cpp @@ -54,11 +54,11 @@ namespace dsm { double meanSpeed{0.}; Size n{0}; if (street->nExitingAgents() == 0) { - n = static_cast(street->waitingAgents().size()); + n = static_cast(street->movingAgents().size()); double alpha{m_alpha / street->capacity()}; meanSpeed = street->maxSpeed() * n * (1. - 0.5 * alpha * (n - 1.)); } else { - for (const auto& agentId : street->waitingAgents()) { + for (const auto& agentId : street->movingAgents()) { meanSpeed += this->agents().at(agentId)->speed(); ++n; } diff --git a/src/dsm/headers/Graph.cpp b/src/dsm/headers/Graph.cpp index 5459e8a91..4de2da45e 100644 --- a/src/dsm/headers/Graph.cpp +++ b/src/dsm/headers/Graph.cpp @@ -45,18 +45,29 @@ namespace dsm { const auto n{static_cast(m_nodes.size())}; std::unordered_map newStreetIds; for (const auto& [streetId, street] : oldStreetSet) { - const auto srcId{street->nodePair().first}; - const auto dstId{street->nodePair().second}; + const auto srcId{street->u()}; + const auto dstId{street->v()}; const auto newStreetId{static_cast(srcId * n + dstId)}; if (m_streets.contains(newStreetId)) { throw std::invalid_argument(buildLog("Street with same id already exists.")); } - if (street->isSpire()) { + if (street->isSpire() && street->isStochastic()) { + m_streets.emplace(newStreetId, + std::make_unique( + newStreetId, + *street, + dynamic_cast(*street).flowRate())); + } else if (street->isStochastic()) { + m_streets.emplace(newStreetId, + std::make_unique( + newStreetId, + *street, + dynamic_cast(*street).flowRate())); + } else if (street->isSpire()) { m_streets.emplace(newStreetId, std::make_unique(newStreetId, *street)); } else { - m_streets.emplace(newStreetId, - std::make_unique(Street{newStreetId, *street})); + m_streets.emplace(newStreetId, std::make_unique(newStreetId, *street)); } newStreetIds.emplace(streetId, newStreetId); } @@ -444,15 +455,27 @@ namespace dsm { pNode = std::make_unique(*pNode, managementTime); return dynamic_cast(*pNode); } - - SpireStreet& Graph::makeSpireStreet(Id streetId) { + StochasticStreet& Graph::makeStochasticStreet(Id streetId, double const flowRate) { if (!m_streets.contains(streetId)) { throw std::invalid_argument( buildLog(std::format("Street with id {} does not exist.", streetId))); } auto& pStreet = m_streets[streetId]; + pStreet = std::make_unique(pStreet->id(), *pStreet, flowRate); + return dynamic_cast(*pStreet); + } + void Graph::makeSpireStreet(Id streetId) { + if (!m_streets.contains(streetId)) { + throw std::invalid_argument( + buildLog(std::format("Street with id {} does not exist.", streetId))); + } + auto& pStreet = m_streets[streetId]; + if (pStreet->isStochastic()) { + pStreet = std::make_unique( + pStreet->id(), *pStreet, dynamic_cast(*pStreet).flowRate()); + return; + } pStreet = std::make_unique(pStreet->id(), *pStreet); - return dynamic_cast(*pStreet); } void Graph::addStreet(std::unique_ptr street) { diff --git a/src/dsm/headers/Graph.hpp b/src/dsm/headers/Graph.hpp index 7144d598e..c588f3f9d 100644 --- a/src/dsm/headers/Graph.hpp +++ b/src/dsm/headers/Graph.hpp @@ -159,10 +159,6 @@ namespace dsm { std::constructible_from) node_t& addNode(Id id, TArgs&&... args); - template - requires(is_node_v> && ...) - void addNodes(Tn&&... nodes); - template requires is_node_v> && (is_node_v> && ...) @@ -182,11 +178,12 @@ namespace dsm { /// @return A reference to the roundabout /// @throws std::invalid_argument if the node does not exist Roundabout& makeRoundabout(Id nodeId); + + StochasticStreet& makeStochasticStreet(Id streetId, double const flowRate); /// @brief Convert an existing street into a spire street /// @param streetId The id of the street to convert to a spire street - /// @return A reference to the spire street /// @throws std::invalid_argument if the street does not exist - SpireStreet& makeSpireStreet(Id streetId); + void makeSpireStreet(Id streetId); /// @brief Convert an existing node into a station /// @param nodeId The id of the node to convert to a station /// @param managementTime The station's management time @@ -286,9 +283,6 @@ namespace dsm { addNode(std::make_unique(id, std::forward(args)...)); return dynamic_cast(*m_nodes[id]); } - template - requires(is_node_v> && ...) - void Graph::addNodes(Tn&&... nodes) {} template requires is_node_v> && (is_node_v> && ...) @@ -313,8 +307,8 @@ namespace dsm { buildLog(std::format("Street with id {} already exists.", street.id()))); } // emplace nodes - const auto srcId{street.nodePair().first}; - const auto dstId{street.nodePair().second}; + auto const srcId{street.u()}; + auto const dstId{street.v()}; if (!m_nodes.contains(srcId)) { m_nodes.emplace(srcId, std::make_unique(srcId)); } diff --git a/src/dsm/headers/Road.cpp b/src/dsm/headers/Road.cpp new file mode 100644 index 000000000..e6692b6f0 --- /dev/null +++ b/src/dsm/headers/Road.cpp @@ -0,0 +1,69 @@ +#include "Road.hpp" +#include "../utility/Logger.hpp" + +#include +#include +#include + +namespace dsm { + double Road::m_meanVehicleLength = 5.; + + Road::Road(Id id, const Road& road) + : Edge( + id, road.nodePair(), road.capacity(), road.transportCapacity(), road.angle()), + m_length{road.length()}, + m_maxSpeed{road.maxSpeed()}, + m_nLanes{road.nLanes()}, + m_name{road.name()} {} + + Road::Road(Id id, + std::pair nodePair, + double length, + double maxSpeed, + int nLanes, + std::string name, + std::optional capacity, + int transportCapacity) + : Edge(id, + std::move(nodePair), + capacity.value_or(std::ceil((length * nLanes) / m_meanVehicleLength)), + transportCapacity), + m_length{length}, + m_maxSpeed{maxSpeed}, + m_nLanes{nLanes}, + m_name{std::move(name)} { + if (!(length > 0.)) { + throw std::invalid_argument(buildLog( + std::format("The length of a road ({}) must be greater than 0.", length))); + } + if (!(maxSpeed > 0.)) { + throw std::invalid_argument(buildLog(std::format( + "The maximum speed of a road ({}) must be greater than 0.", maxSpeed))); + } + if (nLanes < 1) { + throw std::invalid_argument(buildLog(std::format( + "The number of lanes of a road ({}) must be greater than 0.", nLanes))); + } + } + void Road::setMeanVehicleLength(double meanVehicleLength) { + if (!(meanVehicleLength > 0.)) { + throw std::invalid_argument(buildLog(std::format( + "The mean vehicle length ({}) must be greater than 0.", meanVehicleLength))); + } + m_meanVehicleLength = meanVehicleLength; + } + double Road::meanVehicleLength() { return m_meanVehicleLength; } + + void Road::setMaxSpeed(double speed) { + if (speed < 0.) { + throw std::invalid_argument(buildLog( + std::format("The maximum speed of a road ({}) cannot be negative.", speed))); + } + m_maxSpeed = speed; + } + + double Road::length() const { return m_length; } + double Road::maxSpeed() const { return m_maxSpeed; } + int Road::nLanes() const { return m_nLanes; } + std::string Road::name() const { return m_name; } +}; // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/Road.hpp b/src/dsm/headers/Road.hpp new file mode 100644 index 000000000..ca0cf0f65 --- /dev/null +++ b/src/dsm/headers/Road.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "Edge.hpp" + +#include +#include + +namespace dsm { + class Road : public Edge { + protected: + static double m_meanVehicleLength; + double m_length; + double m_maxSpeed; + int m_nLanes; + std::string m_name; + + public: + /// @brief Construct a new Road object starting from an existing road + /// @details The new road has different id but same capacity, length, speed limit, and node pair as the + /// existing road. + /// @param Road The existing road + /// @param id The new road's id + Road(Id id, const Road&); + /// @brief Construct a new Road object + /// @param id The road's id + /// @param nodePair The road's node pair + /// @param length The road's length, in meters (default is the mean vehicle length) + /// @param nLanes The road's number of lanes (default is 1) + /// @param maxSpeed The road's speed limit, in m/s (default is 50 km/h) + /// @param name The road's name (default is an empty string) + /// @param capacity The road's capacity (default is the maximum number of vehicles that can fit in the road) + /// @param transportCapacity The road's transport capacity (default is 1) + Road(Id id, + std::pair nodePair, + double length = m_meanVehicleLength, + double maxSpeed = 13.8888888889, + int nLanes = 1, + std::string name = std::string(), + std::optional capacity = std::nullopt, + int transportCapacity = 1); + /// @brief Set the mean vehicle length, in meters (default is 5) + /// @param meanVehicleLength The mean vehicle length + /// @throws std::invalid_argument If the mean vehicle length is less or equal to 0 + static void setMeanVehicleLength(double meanVehicleLength); + /// @brief Get the mean vehicle length + /// @return double The mean vehicle length + static double meanVehicleLength(); + + /// @brief Set the maximum speed, in meters per second (default is 50 km/h) + /// @param speed The maximum speed + /// @throws std::invalid_argument If the speed is less or equal to 0 + void setMaxSpeed(double speed); + + /// @brief Get the length, in meters + /// @return double The length, in meters + double length() const; + /// @brief Get the maximum speed, in meters per second + /// @return double The maximum speed, in meters per second + double maxSpeed() const; + /// @brief Get the number of lanes + /// @return int The number of lanes + int nLanes() const; + /// @brief Get the name + /// @return std::string The name + std::string name() const; + + virtual void addAgent(Id agentId) = 0; + + virtual int nAgents() const = 0; + virtual int nMovingAgents() const = 0; + virtual int nExitingAgents() const = 0; + virtual double density(bool normalized = false) const = 0; + }; +} // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/RoadDynamics.hpp b/src/dsm/headers/RoadDynamics.hpp index 557bf19a7..78f577a95 100644 --- a/src/dsm/headers/RoadDynamics.hpp +++ b/src/dsm/headers/RoadDynamics.hpp @@ -40,10 +40,15 @@ namespace dsm { template requires(is_numeric_v) class RoadDynamics : public Dynamics> { + private: protected: Time m_previousOptimizationTime; - double m_errorProbability; - double m_passageProbability; + + private: + std::optional m_errorProbability; + std::optional m_passageProbability; + + protected: std::vector m_travelTimes; std::unordered_map m_agentNextStreetId; bool m_forcePriorities; @@ -163,8 +168,8 @@ namespace dsm { RoadDynamics::RoadDynamics(Graph& graph, std::optional seed) : Dynamics>(graph, seed), m_previousOptimizationTime{0}, - m_errorProbability{0.}, - m_passageProbability{1.}, + m_errorProbability{std::nullopt}, + m_passageProbability{std::nullopt}, m_forcePriorities{false} { for (const auto& [streetId, street] : this->m_graph.streetSet()) { m_streetTails.emplace(streetId, 0); @@ -200,11 +205,13 @@ namespace dsm { auto possibleMoves = this->m_graph.adjMatrix().getRow(nodeId, true); if (!pAgent->isRandom()) { 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[pAgent->itineraryId()]; - if (it->destination() != nodeId) { - possibleMoves = it->path().getRow(nodeId, true); + if (!(this->itineraries().empty())) { + if (!(m_errorProbability.has_value() && + uniformDist(this->m_generator) < m_errorProbability)) { + const auto& it = this->m_itineraries[pAgent->itineraryId()]; + if (it->destination() != nodeId) { + possibleMoves = it->path().getRow(nodeId, true); + } } } } @@ -248,6 +255,11 @@ namespace dsm { bool reinsert_agents) { auto const nLanes = pStreet->nLanes(); std::uniform_real_distribution uniformDist{0., 1.}; + if (pStreet->isStochastic() && + (uniformDist(this->m_generator) > + dynamic_cast(*pStreet).flowRate())) { + return; + } for (auto queueIndex = 0; queueIndex < nLanes; ++queueIndex) { if (pStreet->queue(queueIndex).empty()) { continue; @@ -269,7 +281,10 @@ namespace dsm { continue; } } - auto const bCanPass = uniformDist(this->m_generator) < m_passageProbability; + bool bCanPass = true; + if (m_passageProbability.has_value()) { + bCanPass = uniformDist(this->m_generator) < m_passageProbability.value(); + } bool bArrived{false}; if (!bCanPass) { if (pAgent->isRandom()) { @@ -286,7 +301,9 @@ namespace dsm { } } if (bArrived) { - pStreet->dequeue(queueIndex); + if (pStreet->dequeue(queueIndex) == std::nullopt) { + continue; + } m_travelTimes.push_back(pAgent->time()); if (reinsert_agents) { // reset Agent's values @@ -300,7 +317,9 @@ namespace dsm { if (nextStreet->isFull()) { continue; } - pStreet->dequeue(queueIndex); + if (pStreet->dequeue(queueIndex) == std::nullopt) { + continue; + } assert(destinationNode->id() == nextStreet->nodePair().first); if (destinationNode->isIntersection()) { auto& intersection = dynamic_cast(*destinationNode); @@ -424,6 +443,16 @@ namespace dsm { for (auto const& queue : street->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) { diff --git a/src/dsm/headers/Roundabout.hpp b/src/dsm/headers/Roundabout.hpp index 9c9fbfb14..b59862cbe 100644 --- a/src/dsm/headers/Roundabout.hpp +++ b/src/dsm/headers/Roundabout.hpp @@ -30,7 +30,7 @@ namespace dsm { /// @param node An Intersection object Roundabout(const Node& node); - virtual ~Roundabout() = default; + ~Roundabout() = default; /// @brief Put an agent in the node /// @param agentId The agent's id diff --git a/src/dsm/headers/Sensors.cpp b/src/dsm/headers/Sensors.cpp new file mode 100644 index 000000000..29fdd18d3 --- /dev/null +++ b/src/dsm/headers/Sensors.cpp @@ -0,0 +1,23 @@ +#include "Sensors.hpp" + +namespace dsm { + void Counter::increaseInputCounter() { m_counters.first++; } + void Counter::increaseOutputCounter() { m_counters.second++; } + + int Counter::inputCounts(bool reset) { + if (reset) { + int count{0}; + std::swap(count, m_counters.first); + return count; + } + return m_counters.first; + } + int Counter::outputCounts(bool reset) { + if (reset) { + int count{0}; + std::swap(count, m_counters.second); + return count; + } + return m_counters.second; + } +} // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/Sensors.hpp b/src/dsm/headers/Sensors.hpp new file mode 100644 index 000000000..129d9437b --- /dev/null +++ b/src/dsm/headers/Sensors.hpp @@ -0,0 +1,28 @@ +/// @file /src/dsm/headers/Sensors.hpp +/// @brief Defines some sensor classes. +/// +/// @details This file contains the definition of some sensor classes. +/// The Counter class contains two counters to count input and output. + +#pragma once + +#include + +namespace dsm { + /// @brief The Counter class contains two counters to count input and output. + class Counter { + protected: + std::pair m_counters = {0, 0}; // First = in, Second = out + public: + /// @brief Increase the input counter by one + void increaseInputCounter(); + /// @brief Increase the output counter by one + void increaseOutputCounter(); + /// @brief Get the number of input counts registered + /// @param reset If true, the counter is reset to 0. Default is true + int inputCounts(bool reset = true); + /// @brief Get the number of output counts registered + /// @param reset If true, the counter is reset to 0. Default is true + int outputCounts(bool reset = true); + }; +} // namespace dsm \ No newline at end of file diff --git a/src/dsm/headers/Street.cpp b/src/dsm/headers/Street.cpp index 7b10b8353..74d57b79c 100644 --- a/src/dsm/headers/Street.cpp +++ b/src/dsm/headers/Street.cpp @@ -1,16 +1,19 @@ #include "Street.hpp" -namespace dsm { - double Street::m_meanVehicleLength = 5.; +#include +#include +namespace dsm { Street::Street(Id id, const Street& street) - : Edge(id, street.nodePair(), street.capacity(), street.transportCapacity()), - m_length{street.length()}, - m_maxSpeed{street.maxSpeed()}, - m_nLanes{street.nLanes()}, - m_name{street.name()}, - m_angle{street.angle()} { + : Road(id, + street.nodePair(), + street.length(), + street.maxSpeed(), + street.nLanes(), + street.name(), + street.capacity(), + street.transportCapacity()) { for (auto i{0}; i < street.nLanes(); ++i) { m_exitQueues.push_back(dsm::queue()); } @@ -25,27 +28,14 @@ namespace dsm { std::string name, std::optional capacity, int transportCapacity) - : Edge(id, + : Road(id, std::move(nodePair), - capacity.value_or(std::ceil((length * nLanes) / m_meanVehicleLength)), - transportCapacity), - m_length{length}, - m_maxSpeed{maxSpeed}, - m_nLanes{nLanes}, - m_name{std::move(name)}, - m_angle{0.} { - if (!(length > 0.)) { - throw std::invalid_argument(buildLog( - std::format("The length of a street ({}) must be greater than 0.", length))); - } - if (!(maxSpeed > 0.)) { - throw std::invalid_argument(buildLog(std::format( - "The maximum speed of a street ({}) must be greater than 0.", maxSpeed))); - } - if (nLanes < 1) { - throw std::invalid_argument(buildLog(std::format( - "The number of lanes of a street ({}) must be greater than 0.", nLanes))); - } + length, + maxSpeed, + nLanes, + std::move(name), + capacity, + transportCapacity) { m_exitQueues.resize(nLanes); for (auto i{0}; i < nLanes; ++i) { m_exitQueues.push_back(dsm::queue()); @@ -73,57 +63,32 @@ namespace dsm { } } - void Street::setMaxSpeed(double speed) { - if (speed < 0.) { - throw std::invalid_argument(buildLog( - std::format("The maximum speed of a street ({}) cannot be negative.", speed))); - } - m_maxSpeed = speed; - } - void Street::setAngle(std::pair srcNode, - std::pair dstNode) { - // N.B.: lat, lon <==> y, x - double delta_y{dstNode.first - srcNode.first}; - double delta_x{dstNode.second - srcNode.second}; - double angle{std::atan2(delta_y, delta_x)}; - if (angle < 0.) { - angle += 2 * std::numbers::pi; - } - this->setAngle(angle); - } - void Street::setAngle(double angle) { - if (std::abs(angle) > 2 * std::numbers::pi) { - throw std::invalid_argument(buildLog(std::format( - "The angle of a street ({}) must be between - 2 * pi and 2 * pi.", angle))); - } - m_angle = angle; - } - void Street::setMeanVehicleLength(double meanVehicleLength) { - if (!(meanVehicleLength > 0.)) { - throw std::invalid_argument(buildLog(std::format( - "The mean vehicle length ({}) must be greater than 0.", meanVehicleLength))); - } - m_meanVehicleLength = meanVehicleLength; - } + std::vector const& Street::movingAgents() const { return m_movingAgents; } void Street::addAgent(Id agentId) { - assert((void("Agent is already on the street."), !m_waitingAgents.contains(agentId))); + assert((void("Agent is already on the street."), + std::find(m_movingAgents.cbegin(), m_movingAgents.cend(), agentId) == + m_movingAgents.cend())); for (auto const& queue : m_exitQueues) { for (auto const& id : queue) { assert((void("Agent is already in queue."), id != agentId)); } } - m_waitingAgents.insert(agentId); + m_movingAgents.push_back(agentId); ; } void Street::enqueue(Id agentId, size_t index) { - assert((void("Agent is not on the street."), m_waitingAgents.contains(agentId))); + assert((void("Agent is not on the street."), + std::find(m_movingAgents.cbegin(), m_movingAgents.cend(), agentId) != + m_movingAgents.cend())); for (auto const& queue : m_exitQueues) { for (auto const& id : queue) { assert((void("Agent is already in queue."), id != agentId)); } } - m_waitingAgents.erase(agentId); + m_movingAgents.erase( + std::remove(m_movingAgents.begin(), m_movingAgents.end(), agentId), + m_movingAgents.end()); m_exitQueues[index].push(agentId); } std::optional Street::dequeue(size_t index) { @@ -136,7 +101,7 @@ namespace dsm { } int Street::nAgents() const { - auto nAgents{static_cast(m_waitingAgents.size())}; + auto nAgents{static_cast(m_movingAgents.size())}; for (const auto& queue : m_exitQueues) { nAgents += queue.size(); } @@ -148,58 +113,73 @@ namespace dsm { : nAgents() / (m_length * m_nLanes); } - Size Street::nExitingAgents() const { - Size nAgents{0}; + int Street::nExitingAgents() const { + int nAgents{0}; for (const auto& queue : m_exitQueues) { nAgents += queue.size(); } return nAgents; } - double Street::deltaAngle(double const previousStreetAngle) const { - double deltaAngle{m_angle - previousStreetAngle}; - if (deltaAngle > std::numbers::pi) { - deltaAngle -= 2 * std::numbers::pi; - } else if (deltaAngle < -std::numbers::pi) { - deltaAngle += 2 * std::numbers::pi; - } - return deltaAngle; - } + StochasticStreet::StochasticStreet(Id id, const Street& street, double flowRate) + : Street(id, street) { + setFlowRate(flowRate); + } + StochasticStreet::StochasticStreet(Id id, + std::pair nodePair, + double length, + double maxSpeed, + int nLanes, + std::string name, + double flowRate, + std::optional capacity, + int transportCapacity) + : Street(id, + std::move(nodePair), + length, + maxSpeed, + nLanes, + std::move(name), + capacity, + transportCapacity) { + setFlowRate(flowRate); + } + void StochasticStreet::setFlowRate(double const flowRate) { + if (flowRate < 0. || flowRate > 1.) { + throw std::invalid_argument( + buildLog(std::format("Flow rate ({}) must be between 0 and 1", flowRate))); + } + m_flowRate = flowRate; + } + double StochasticStreet::flowRate() const { return m_flowRate; } + bool StochasticStreet::isStochastic() const { return true; } void SpireStreet::addAgent(Id agentId) { Street::addAgent(agentId); - ++m_agentCounterIn; + increaseInputCounter(); } - Size SpireStreet::inputCounts(bool resetValue) { - if (!resetValue) - return m_agentCounterIn; - Size flow = m_agentCounterIn; - m_agentCounterIn = 0; - m_agentCounterOut = 0; - return flow; - } - Size SpireStreet::outputCounts(bool resetValue) { - if (!resetValue) - return m_agentCounterOut; - Size flow = m_agentCounterOut; - m_agentCounterIn = 0; - m_agentCounterOut = 0; - return flow; - } - int SpireStreet::meanFlow() { - int flow = static_cast(m_agentCounterIn) - static_cast(m_agentCounterOut); - m_agentCounterIn = 0; - m_agentCounterOut = 0; - return flow; - } + int SpireStreet::meanFlow() { return inputCounts() - outputCounts(); } std::optional SpireStreet::dequeue(size_t index) { - std::optional id = Street::dequeue(index); + auto const& id = Street::dequeue(index); if (id.has_value()) { - ++m_agentCounterOut; + increaseOutputCounter(); } return id; } + void StochasticSpireStreet::addAgent(Id agentId) { + Street::addAgent(agentId); + increaseInputCounter(); + } + + int StochasticSpireStreet::meanFlow() { return inputCounts() - outputCounts(); } + std::optional StochasticSpireStreet::dequeue(size_t index) { + auto const& id = Street::dequeue(index); + if (id.has_value()) { + increaseOutputCounter(); + } + return id; + } }; // namespace dsm diff --git a/src/dsm/headers/Street.hpp b/src/dsm/headers/Street.hpp index c5c3de29e..474fd7eda 100644 --- a/src/dsm/headers/Street.hpp +++ b/src/dsm/headers/Street.hpp @@ -19,10 +19,11 @@ #include #include #include +#include -#include "Edge.hpp" +#include "Road.hpp" #include "Agent.hpp" -#include "Node.hpp" +#include "Sensors.hpp" #include "../utility/TypeTraits/is_numeric.hpp" #include "../utility/queue.hpp" #include "../utility/Logger.hpp" @@ -30,19 +31,11 @@ namespace dsm { /// @brief The Street class represents a street in the network. - /// @tparam Id, The type of the street's id. It must be an unsigned integral type. - /// @tparam Size, The type of the street's capacity. It must be an unsigned integral type. - class Street : public Edge { + class Street : public Road { private: std::vector> m_exitQueues; std::vector m_laneMapping; - std::set m_waitingAgents; - double m_length; - double m_maxSpeed; - int m_nLanes; - std::string m_name; - double m_angle; - static double m_meanVehicleLength; + std::vector m_movingAgents; public: /// @brief Construct a new Street object starting from an existing street @@ -62,7 +55,7 @@ namespace dsm { /// @param transportCapacity The street's transport capacity (default is 1) Street(Id id, std::pair nodePair, - double length = m_meanVehicleLength, + double length = Road::meanVehicleLength(), double maxSpeed = 13.8888888889, int nLanes = 1, std::string name = std::string(), @@ -75,29 +68,14 @@ namespace dsm { inline void setQueue(dsm::queue queue, size_t index) { m_exitQueues[index] = std::move(queue); } - /// @brief Set the street's speed limit - /// @param speed The street's speed limit - /// @throw std::invalid_argument, If the speed is negative - void setMaxSpeed(double speed); - /// @brief Set the street's angle - /// @param srcNode The source node of the street - /// @param dstNode The destination node of the street - void setAngle(std::pair srcNode, std::pair dstNode); - /// @brief Set the street's angle - /// @param angle The street's angle - /// @throw std::invalid_argument If the angle is negative or greater than 2 * pi - void setAngle(double angle); /// @brief Set the mean vehicle length /// @param meanVehicleLength The mean vehicle length /// @throw std::invalid_argument If the mean vehicle length is negative static void setMeanVehicleLength(double meanVehicleLength); - /// @brief Get the street's length - /// @return double, The street's length - double length() const { return m_length; } /// @brief Get the street's waiting agents /// @return std::set, The street's waiting agents - const std::set& waitingAgents() const { return m_waitingAgents; } + std::vector const& movingAgents() const; /// @brief Get the street's queue /// @return dsm::queue, The street's queue const dsm::queue& queue(size_t index) const { return m_exitQueues[index]; } @@ -106,37 +84,22 @@ namespace dsm { const std::vector>& exitQueues() const { return m_exitQueues; } /// @brief Get the number of agents on the street /// @return Size, The number of agents on the street - int nAgents() const; + int nAgents() const final; /// @brief Get the street's density in \f$m^{-1}\f$ or in \f$a.u.\f$, if normalized /// @param normalized If true, the street's density is normalized by the street's capacity /// @return double, The street's density - double density(bool normalized = false) const; + double density(bool normalized = false) const final; /// @brief Check if the street is full /// @return bool, True if the street is full, false otherwise bool isFull() const final { return nAgents() == m_capacity; } - /// @brief Get the street's speed limit - /// @return double, The street's speed limit - double maxSpeed() const { return m_maxSpeed; } - /// @brief Get the street's angle - /// @return double The street's angle - double angle() const { return m_angle; } - /// @brief Get the street's number of lanes - /// @return int The street's number of lanes - int nLanes() const { return m_nLanes; } - /// @brief Get the street's name - /// @return std::string_view The street's name - std::string_view name() const { return m_name; } + int nMovingAgents() const override { return m_movingAgents.size(); } /// @brief Get the number of agents on all queues /// @return Size The number of agents on all queues - Size nExitingAgents() const; - /// @brief Get the delta angle between the street and the previous street, normalized between -pi and pi - /// @param previousStreetAngle The angle of the previous street - /// @return double The delta angle between the street and the previous street - double deltaAngle(double const previousStreetAngle) const; + int nExitingAgents() const final; inline std::vector const& laneMapping() const { return m_laneMapping; } - virtual void addAgent(Id agentId); + void addAgent(Id agentId) override; /// @brief Add an agent to the street's queue /// @param agentId The id of the agent to add to the street's queue /// @throw std::runtime_error If the street's queue is full @@ -146,16 +109,42 @@ namespace dsm { /// @brief Check if the street is a spire /// @return bool True if the street is a spire, false otherwise virtual bool isSpire() const { return false; }; + virtual bool isStochastic() const { return false; }; + }; + + /// @brief A stochastic street is a street with a flow rate parameter + /// @details The Stochastic Street is used to replace traffic lights with a lower level of detail. + /// The idea is to model the flow of agents in a street as a stochastic process, limiting + /// the number of agents that can exit using a parameter in [0, 1]. + /// Thus, the flow rate parameter represents the ratio between the green time of the + /// traffic light and the total time of the traffic light cycle. + class StochasticStreet : public Street { + private: + double m_flowRate; + + public: + StochasticStreet(Id id, const Street& street, double flowRate); + StochasticStreet(Id id, + std::pair nodePair, + double length = Road::meanVehicleLength(), + double maxSpeed = 13.8888888889, + int nLanes = 1, + std::string name = std::string(), + double flowRate = 1., + std::optional capacity = std::nullopt, + int transportCapacity = 1); + + void setFlowRate(double const flowRate); + double flowRate() const; + + bool isStochastic() const final; }; /// @brief The SpireStreet class represents a street which is able to count agent flows in both input and output. /// @tparam Id The type of the street's id /// @tparam Size The type of the street's capacity - class SpireStreet : public Street { + class SpireStreet : public Street, public Counter { private: - Size m_agentCounterIn = 0; - Size m_agentCounterOut = 0; - public: using Street::Street; ~SpireStreet() = default; @@ -163,16 +152,29 @@ namespace dsm { /// @brief Add an agent to the street's queue /// @param agentId The id of the agent to add to the street's queue /// @throw std::runtime_error If the street's queue is full - void addAgent(Id agentId) override; + void addAgent(Id agentId) final; + + /// @brief Get the mean flow of the street + /// @return int The flow of the street, i.e. the difference between input and output flows + /// @details Once the flow is retrieved, bothh the input and output flows are reset to 0. + /// Notice that this flow is positive iff the input flow is greater than the output flow. + int meanFlow(); + /// @brief Remove an agent from the street's queue + /// @return std::optional The id of the agent removed from the street's queue + std::optional dequeue(size_t index) final; + /// @brief Check if the street is a spire + /// @return bool True if the street is a spire, false otherwise + bool isSpire() const final { return true; }; + }; + + class StochasticSpireStreet : public StochasticStreet, public Counter { + public: + using StochasticStreet::StochasticStreet; + /// @brief Add an agent to the street's queue + /// @param agentId The id of the agent to add to the street's queue + /// @throw std::runtime_error If the street's queue is full + void addAgent(Id agentId) final; - /// @brief Get the input counts of the street - /// @param resetValue If true, the counter is reset to 0 together with the output counter. - /// @return Size The input counts of the street - Size inputCounts(bool resetValue = false); - /// @brief Get the output counts of the street - /// @param resetValue If true, the counter is reset to 0 together with the input counter. - /// @return Size The output counts of the street - Size outputCounts(bool resetValue = false); /// @brief Get the mean flow of the street /// @return int The flow of the street, i.e. the difference between input and output flows /// @details Once the flow is retrieved, bothh the input and output flows are reset to 0. @@ -180,7 +182,7 @@ namespace dsm { int meanFlow(); /// @brief Remove an agent from the street's queue /// @return std::optional The id of the agent removed from the street's queue - std::optional dequeue(size_t index) override; + std::optional dequeue(size_t index) final; /// @brief Check if the street is a spire /// @return bool True if the street is a spire, false otherwise bool isSpire() const final { return true; }; diff --git a/src/dsm/headers/TrafficLight.hpp b/src/dsm/headers/TrafficLight.hpp index 38dff4099..33190e339 100644 --- a/src/dsm/headers/TrafficLight.hpp +++ b/src/dsm/headers/TrafficLight.hpp @@ -68,6 +68,8 @@ namespace dsm { TrafficLight(Node const& node, Delay const cycleTime, Delay const counter = 0) : Intersection{node}, m_cycleTime{cycleTime}, m_counter{counter} {} + ~TrafficLight() = default; + TrafficLight& operator++(); /// @brief Get the maximum green time over every cycle /// @param priorityStreets bool, if true, only the priority streets are considered; diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index 93fcb4e8c..d7533a302 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -10,6 +10,7 @@ using Dynamics = dsm::FirstOrderDynamics; using Graph = dsm::Graph; using SparseMatrix = dsm::SparseMatrix; +using Road = dsm::Road; using Street = dsm::Street; using SpireStreet = dsm::SpireStreet; using Agent = dsm::Agent; @@ -816,7 +817,7 @@ TEST_CASE("Dynamics") { "A dynamics object with four streets, one agent for each street, two " "itineraries " "and a roundabout") { - Street::setMeanVehicleLength(10.); + Road::setMeanVehicleLength(10.); Street s1{0, std::make_pair(0, 1), 10., 10.}; Street s2{1, std::make_pair(2, 1), 10., 10.}; Street s3{2, std::make_pair(1, 0), 10., 10.}; @@ -886,7 +887,7 @@ TEST_CASE("Dynamics") { /// GIVEN: a dynamics object /// WHEN: we evolve the dynamics /// THEN: the agent mean speed is the same as the street mean speed - Street::setMeanVehicleLength(2.); + Road::setMeanVehicleLength(2.); Street s1{0, std::make_pair(0, 1), 20., 20.}; Street s2{1, std::make_pair(1, 2), 30., 15.}; Street s3{2, std::make_pair(3, 1), 30., 15.}; @@ -910,7 +911,7 @@ TEST_CASE("Dynamics") { meanSpeed += agent->speed(); } meanSpeed /= (dynamics.graph().streetSet().at(1)->nExitingAgents() + - dynamics.graph().streetSet().at(1)->waitingAgents().size()); + dynamics.graph().streetSet().at(1)->movingAgents().size()); CHECK_EQ(dynamics.streetMeanSpeed(1), meanSpeed); // I don't think the mean speed of agents should be equal to the street's // one... CHECK_EQ(dynamics.streetMeanSpeed().mean, diff --git a/test/Test_graph.cpp b/test/Test_graph.cpp index 3755387c1..74ecd003f 100644 --- a/test/Test_graph.cpp +++ b/test/Test_graph.cpp @@ -4,6 +4,7 @@ #include "Graph.hpp" #include "Node.hpp" +#include "Road.hpp" #include "Street.hpp" #include "SparseMatrix.hpp" @@ -12,6 +13,7 @@ using Graph = dsm::Graph; using SparseMatrix = dsm::SparseMatrix; using Street = dsm::Street; +using Road = dsm::Road; using Path = std::vector; template @@ -30,7 +32,7 @@ bool checkPath(const std::vector& path1, const std::vector& path2) { } TEST_CASE("Graph") { - Street::setMeanVehicleLength(5.); + Road::setMeanVehicleLength(5.); SUBCASE("Constructor_1") { Street street{1, std::make_pair(0, 1)}; Graph graph{}; diff --git a/test/Test_street.cpp b/test/Test_street.cpp index bf5325bce..0e4c4d5b1 100644 --- a/test/Test_street.cpp +++ b/test/Test_street.cpp @@ -12,10 +12,11 @@ using Agent = dsm::Agent; using Intersection = dsm::Intersection; using Street = dsm::Street; +using Road = dsm::Road; using SpireStreet = dsm::SpireStreet; TEST_CASE("Street") { - Street::setMeanVehicleLength(1.); + Road::setMeanVehicleLength(1.); SUBCASE("Constructor_1") { /*This tests the constructor that takes an Id, capacity, and length. GIVEN: An Id, capacity, and length