diff --git a/CMakeLists.txt b/CMakeLists.txt index 76198412..5ec23f7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16.0) -project(dms VERSION 1.2.0 LANGUAGES CXX) +project(dms VERSION 1.2.1 LANGUAGES CXX) # set the C++ standard set(CMAKE_CXX_STANDARD 20) diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index 79ac1e67..c89d42ea 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -134,7 +134,7 @@ namespace dsm { /// @brief Get the graph /// @return const Graph&, The graph - const Graph& graph() const; + const Graph& graph() const { return m_graph; }; /// @brief Get the itineraries /// @return const std::unordered_map>&, The itineraries const std::unordered_map>>& itineraries() const; @@ -483,13 +483,6 @@ namespace dsm { ++this->m_time; } - template - requires(std::unsigned_integral && std::unsigned_integral && - is_numeric_v) - const Graph& Dynamics::graph() const { - return m_graph; - } - template requires(std::unsigned_integral && std::unsigned_integral && is_numeric_v) @@ -737,9 +730,6 @@ namespace dsm { m_graph.streetSet().cend(), 0., [](double sum, const auto& street) { - if (!street.second->isSpire()) { - return sum; - } return sum + street.second->density(); }) / m_graph.streetSet().size()}; @@ -748,9 +738,6 @@ namespace dsm { m_graph.streetSet().cend(), 0., [mean](double sum, const auto& street) { - if (!street.second->isSpire()) { - return sum; - } return sum + std::pow(street.second->density() - mean, 2); }) / (m_graph.streetSet().size() - 1)}; @@ -763,9 +750,6 @@ namespace dsm { std::vector flows; flows.reserve(m_graph.streetSet().size()); for (const auto& [streetId, street] : m_graph.streetSet()) { - if (!street->isSpire()) { - continue; - } auto speedOpt{this->streetMeanSpeed(streetId)}; if (speedOpt.has_value()) { double flow{street->density() * speedOpt.value()}; @@ -784,9 +768,6 @@ namespace dsm { std::vector flows; flows.reserve(m_graph.streetSet().size()); for (const auto& [streetId, street] : m_graph.streetSet()) { - if (!street->isSpire()) { - continue; - } if (above && street->density() > threshold) { auto speedOpt{this->streetMeanSpeed(streetId)}; if (speedOpt.has_value()) { @@ -880,7 +861,7 @@ namespace dsm { std::optional FirstOrderDynamics::streetMeanSpeed( Id streetId) const { const auto& street{this->m_graph.streetSet().at(streetId)}; - if (street->queue().empty() || !street->isSpire()) { + if (street->queue().empty()) { return std::nullopt; } if (this->m_agents.at(street->queue().front())->delay() > 0) { @@ -923,9 +904,6 @@ namespace dsm { std::vector speeds; speeds.reserve(this->m_graph.streetSet().size()); for (const auto& [streetId, street] : this->m_graph.streetSet()) { - if (!street->isSpire()) { - continue; - } if (above) { if (street->density() > threshold) { auto speedOpt{this->streetMeanSpeed(streetId)}; diff --git a/src/dsm/headers/Graph.hpp b/src/dsm/headers/Graph.hpp index 41e7776a..ef199c50 100644 --- a/src/dsm/headers/Graph.hpp +++ b/src/dsm/headers/Graph.hpp @@ -154,6 +154,10 @@ namespace dsm { template requires(std::unsigned_integral) void makeTrafficLight(Id nodeId); + /// @brief Convert an existing street into a spire street + /// @param streetId The id of the street to convert to a spire street + /// @throws std::invalid_argument if the street does not exist + void makeSpireStreet(Id streetId); /// @brief Add a street to the graph /// @param street A std::shared_ptr to the street to add @@ -563,6 +567,15 @@ namespace dsm { auto& pNode = m_nodes[nodeId]; pNode = std::make_unique>(*pNode); } + template + requires(std::unsigned_integral && std::unsigned_integral) + void Graph::makeSpireStreet(Id streetId) { + if (!m_streets.contains(streetId)) { + throw std::invalid_argument(buildLog("Street does not exist.")); + } + auto& pStreet = m_streets[streetId]; + pStreet = std::make_unique>(pStreet->id(), *pStreet); + } template requires(std::unsigned_integral && std::unsigned_integral) diff --git a/src/dsm/headers/Node.hpp b/src/dsm/headers/Node.hpp index 013c6855..3f898cf4 100644 --- a/src/dsm/headers/Node.hpp +++ b/src/dsm/headers/Node.hpp @@ -82,7 +82,7 @@ namespace dsm { virtual bool isGreen() const; virtual bool isGreen(Id) const; - virtual void increaseCounter() {}; + virtual void increaseCounter(){}; virtual bool isTrafficLight() const { return false; } diff --git a/src/dsm/headers/Street.hpp b/src/dsm/headers/Street.hpp index 88b0e8f5..533b6676 100644 --- a/src/dsm/headers/Street.hpp +++ b/src/dsm/headers/Street.hpp @@ -39,7 +39,6 @@ namespace dsm { Id m_id; Size m_capacity; Size m_transportCapacity; - bool m_isSpire; public: Street() = delete; @@ -70,6 +69,8 @@ namespace dsm { /// @param nodePair The street's node pair Street(Id id, Size capacity, double len, double maxSpeed, std::pair nodePair); + virtual ~Street() = default; + /// @brief Set the street's id /// @param id The street's id void setId(Id id); @@ -111,11 +112,6 @@ namespace dsm { /// @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 street's spire status - /// @param isSpire The street's spire status - /// @details A spire is a street from which you can extract data, e.g. density. However, this - /// parameter must be managed by the dynamics. - void setIsSpire(bool isSpire); /// @brief Get the street's id /// @return Id, The street's id @@ -148,12 +144,13 @@ namespace dsm { double angle() const; /// @brief Add an agent to the street's queue /// @param agentId The id of the agent to add to the street's queue - void enqueue(Id agentId); + /// @throw std::runtime_error If the street's queue is full + virtual void enqueue(Id agentId); /// @brief Remove an agent from the street's queue - std::optional dequeue(); + virtual std::optional dequeue(); /// @brief Check if the street is a spire /// @return bool True if the street is a spire, false otherwise - bool isSpire() const; + virtual bool isSpire() const { return false; }; }; template @@ -165,8 +162,7 @@ namespace dsm { m_angle{street.angle()}, m_id{id}, m_capacity{street.capacity()}, - m_transportCapacity{street.transportCapacity()}, - m_isSpire{true} {} + m_transportCapacity{street.transportCapacity()} {} template requires(std::unsigned_integral && std::unsigned_integral) @@ -177,8 +173,7 @@ namespace dsm { m_angle{0.}, m_id{index}, m_capacity{1}, - m_transportCapacity{std::numeric_limits::max()}, - m_isSpire{true} {} + m_transportCapacity{std::numeric_limits::max()} {} template requires(std::unsigned_integral && std::unsigned_integral) @@ -189,8 +184,7 @@ namespace dsm { m_angle{0.}, m_id{id}, m_capacity{capacity}, - m_transportCapacity{std::numeric_limits::max()}, - m_isSpire{true} {} + m_transportCapacity{std::numeric_limits::max()} {} template requires(std::unsigned_integral && std::unsigned_integral) @@ -201,8 +195,7 @@ namespace dsm { m_angle{0.}, m_id{id}, m_capacity{capacity}, - m_transportCapacity{std::numeric_limits::max()}, - m_isSpire{true} { + m_transportCapacity{std::numeric_limits::max()} { this->setMaxSpeed(maxSpeed); } @@ -279,11 +272,6 @@ namespace dsm { } m_angle = angle; } - template - requires(std::unsigned_integral && std::unsigned_integral) - void Street::setIsSpire(bool isSpire) { - m_isSpire = isSpire; - } template requires(std::unsigned_integral && std::unsigned_integral) @@ -334,9 +322,7 @@ namespace dsm { requires(std::unsigned_integral && std::unsigned_integral) void Street::enqueue(Id agentId) { if (m_queue.size() == m_capacity) { - std::string errorMsg{"Error at line " + std::to_string(__LINE__) + " in file " + - __FILE__ + ": " + "The street's queue is full."}; - throw std::runtime_error(errorMsg); + throw std::runtime_error(buildLog("The street's queue is full.")); } for (auto const& id : m_queue) { if (id == agentId) { @@ -355,10 +341,128 @@ namespace dsm { m_queue.pop(); return id; } + + /// @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 template requires(std::unsigned_integral && std::unsigned_integral) - bool Street::isSpire() const { - return m_isSpire; + class SpireStreet : public Street { + private: + Size m_agentCounterIn; + Size m_agentCounterOut; + + public: + SpireStreet() = delete; + /// @brief Construct a new SpireStreet object starting from an existing street + /// @param id The street's id + /// @param street The existing street + SpireStreet(Id id, const Street& street); + /// @brief Construct a new SpireStreet object + /// @param id The street's id + /// @param capacity The street's capacity + /// @param len The street's length + /// @param nodePair The street's node pair + SpireStreet(Id id, Size capacity, double len, std::pair nodePair); + /// @brief Construct a new SpireStreet object + /// @param id The street's id + /// @param capacity The street's capacity + /// @param len The street's length + /// @param maxSpeed The street's speed limit + /// @param nodePair The street's node pair + SpireStreet( + Id id, Size capacity, double len, double maxSpeed, std::pair nodePair); + ~SpireStreet() = default; + + /// @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 enqueue(Id agentId) override; + + /// @brief Get the input flow of the street + /// @return Size The input flow of the street + /// @details Once the input flow is retrieved, it is reset to 0 together with the output flow. + Size inputFlow(); + /// @brief Get the output flow of the street + /// @return Size The output flow of the street + /// @details Once the output flow is retrieved, it is reset to 0 together with the input flow. + Size outputFlow(); + /// @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() override; + /// @brief Check if the street is a spire + /// @return bool True if the street is a spire, false otherwise + bool isSpire() const override { return true; }; + }; + + template + requires(std::unsigned_integral && std::unsigned_integral) + SpireStreet::SpireStreet(Id id, const Street& street) + : Street(id, street), m_agentCounterIn{0}, m_agentCounterOut{0} {} + + template + requires(std::unsigned_integral && std::unsigned_integral) + SpireStreet::SpireStreet(Id id, + Size capacity, + double len, + std::pair nodePair) + : Street(id, capacity, len, nodePair), + m_agentCounterIn{0}, + m_agentCounterOut{0} {} + + template + requires(std::unsigned_integral && std::unsigned_integral) + SpireStreet::SpireStreet( + Id id, Size capacity, double len, double maxSpeed, std::pair nodePair) + : Street(id, capacity, len, maxSpeed, nodePair), + m_agentCounterIn{0}, + m_agentCounterOut{0} {} + + template + requires(std::unsigned_integral && std::unsigned_integral) + void SpireStreet::enqueue(Id agentId) { + Street::enqueue(agentId); + ++m_agentCounterIn; + } + + template + requires(std::unsigned_integral && std::unsigned_integral) + Size SpireStreet::inputFlow() { + Size flow = m_agentCounterIn; + m_agentCounterIn = 0; + m_agentCounterOut = 0; + return flow; + } + template + requires(std::unsigned_integral && std::unsigned_integral) + Size SpireStreet::outputFlow() { + Size flow = m_agentCounterOut; + m_agentCounterIn = 0; + m_agentCounterOut = 0; + return flow; + } + template + requires(std::unsigned_integral && std::unsigned_integral) + int SpireStreet::meanFlow() { + int flow = static_cast(m_agentCounterIn) - static_cast(m_agentCounterOut); + m_agentCounterIn = 0; + m_agentCounterOut = 0; + return flow; + } + + template + requires(std::unsigned_integral && std::unsigned_integral) + std::optional SpireStreet::dequeue() { + std::optional id = Street::dequeue(); + if (id.has_value()) { + ++m_agentCounterOut; + } + return id; } }; // namespace dsm diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b866058f..388eee5d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16.0) -project(dsm_tests VERSION 1.2.0 LANGUAGES CXX) +project(dsm_tests VERSION 1.2.1 LANGUAGES CXX) # Set the C++ standard set(CMAKE_CXX_STANDARD 20) diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index ef728840..043af5cd 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -46,6 +46,13 @@ TEST_CASE("Dynamics") { CHECK(dynamics.graph().nodeSet().at(0)->isTrafficLight()); } } + WHEN("We transorm a street into a spire and create the dynamcis") { + graph.makeSpireStreet(8); + Dynamics dynamics(graph); + THEN("The street is a spire") { + CHECK(dynamics.graph().streetSet().at(8)->isSpire()); + } + } } } SUBCASE("addAgentsUniformly") { @@ -445,7 +452,5 @@ TEST_CASE("Dynamics") { CHECK_EQ(dynamics.graph().streetSet().at(1)->queue().size(), 3); CHECK(dynamics.streetMeanSpeed(1).has_value()); CHECK_EQ(dynamics.streetMeanSpeed(1).value(), meanSpeed); - dynamics.graph().streetSet().at(1)->setIsSpire(false); - CHECK_FALSE(dynamics.streetMeanSpeed(1).has_value()); } } diff --git a/test/Test_graph.cpp b/test/Test_graph.cpp index a38b4bd6..2dac2312 100644 --- a/test/Test_graph.cpp +++ b/test/Test_graph.cpp @@ -201,6 +201,19 @@ TEST_CASE("Graph") { } } } + SUBCASE("make spire street") { + GIVEN("A graph object with two nodes and one street") { + Graph graph{}; + graph.addStreet(Street{0, 1, 1., std::make_pair(0, 1)}); + graph.buildAdj(); + WHEN("We make the street a spire street") { + graph.makeSpireStreet(1); + THEN("The street is a spire street") { + CHECK(graph.streetSet().at(1)->isSpire()); + } + } + } + } } TEST_CASE("Dijkstra") { diff --git a/test/Test_street.cpp b/test/Test_street.cpp index 9ea6ba6d..e60bd27e 100644 --- a/test/Test_street.cpp +++ b/test/Test_street.cpp @@ -11,6 +11,7 @@ using Agent = dsm::Agent; using Node = dsm::Node; using Street = dsm::Street; +using SpireStreet = dsm::SpireStreet; TEST_CASE("Street") { SUBCASE("Constructor_1") { @@ -162,3 +163,66 @@ TEST_CASE("Street") { CHECK_THROWS(street.setAngle(7.)); } } + +TEST_CASE("SpireStreet") { + SUBCASE("Input flow") { + GIVEN("A spire street") { + SpireStreet street{1, 4, 3.5, std::make_pair(0, 1)}; + WHEN("An agent is enqueued") { + street.enqueue(1); + THEN("The density is updated") { CHECK_EQ(street.density(), 0.25); } + THEN("Input flow is one") { CHECK_EQ(street.inputFlow(), 1); } + THEN("Output flow is zero") { CHECK_EQ(street.outputFlow(), 0); } + THEN("Mean flow is one") { CHECK_EQ(street.meanFlow(), 1); } + } + WHEN("Three agents are enqueued") { + street.enqueue(1); + street.enqueue(2); + street.enqueue(3); + THEN("The density is updated") { CHECK_EQ(street.density(), 0.75); } + THEN("Input flow is three") { CHECK_EQ(street.inputFlow(), 3); } + THEN("Output flow is zero") { CHECK_EQ(street.outputFlow(), 0); } + THEN("Mean flow is three") { CHECK_EQ(street.meanFlow(), 3); } + } + WHEN("An agent is dequeued") { + street.enqueue(1); + street.dequeue(); + THEN("The density is updated") { CHECK_EQ(street.density(), 0); } + THEN("Input flow is one") { CHECK_EQ(street.inputFlow(), 1); } + THEN("Output flow is one") { CHECK_EQ(street.outputFlow(), 1); } + THEN("Mean flow is zero") { CHECK_EQ(street.meanFlow(), 0); } + } + WHEN("Three agents are dequeued") { + street.enqueue(1); + street.enqueue(2); + street.enqueue(3); + street.dequeue(); + street.dequeue(); + street.dequeue(); + THEN("The density is updated") { CHECK_EQ(street.density(), 0); } + THEN("Input flow is three") { CHECK_EQ(street.inputFlow(), 3); } + THEN("Output flow is three") { CHECK_EQ(street.outputFlow(), 3); } + THEN("Mean flow is zero") { CHECK_EQ(street.meanFlow(), 0); } + } + WHEN("Input is greater than output") { + street.enqueue(1); + street.enqueue(2); + street.dequeue(); + street.dequeue(); + street.enqueue(3); + THEN("The density is updated") { CHECK_EQ(street.density(), 0.25); } + THEN("Mean flow is one") { CHECK_EQ(street.meanFlow(), 1); } + } + WHEN("Output is greater than input") { + street.enqueue(1); + street.enqueue(2); + street.meanFlow(); + street.enqueue(3); + street.dequeue(); + street.dequeue(); + THEN("The density is updated") { CHECK_EQ(street.density(), 0.25); } + THEN("Mean flow is minus one") { CHECK_EQ(street.meanFlow(), -1); } + } + } + } +}