diff --git a/examples/slow_charge_rb.cpp b/examples/slow_charge_rb.cpp index f51b0f0ed..35db89067 100644 --- a/examples/slow_charge_rb.cpp +++ b/examples/slow_charge_rb.cpp @@ -135,7 +135,7 @@ int main(int argc, char** argv) { std::cout << "Number of exits: " << n << '\n'; dynamics.setErrorProbability(0.05); - dynamics.setMaxFlowPercentage(0.7707); + dynamics.setPassageProbability(0.7707); // dynamics.setForcePriorities(true); dynamics.setSpeedFluctuationSTD(0.1); diff --git a/examples/stalingrado.cpp b/examples/stalingrado.cpp index cd82164e6..1b78a310c 100644 --- a/examples/stalingrado.cpp +++ b/examples/stalingrado.cpp @@ -51,7 +51,7 @@ int main() { Street s01{1, 2281 / 8, 2281., 13.9, std::make_pair(0, 1)}; Street s12{7, 118 / 8, 118., 13.9, std::make_pair(1, 2)}; Street s23{13, 222 / 8, 222., 13.9, std::make_pair(2, 3)}; - Street s34{19, 651 / 4, 651., 13.9, std::make_pair(3, 4)}; + Street s34{19, 1, 651., 13.9, std::make_pair(3, 4), 2}; // Viale Aldo Moro TrafficLight tl1{1, 132}; tl1.setCycle(s01.id(), dsm::Direction::ANY, {62, 0}); @@ -76,6 +76,8 @@ int main() { graph.addNode(std::make_unique(tl4)); graph.addStreets(s01, s12, s23, s34); graph.buildAdj(); + graph.adjustNodeCapacities(); + graph.normalizeStreetCapacities(); auto& spire = graph.makeSpireStreet(19); std::cout << "Intersections: " << graph.nNodes() << '\n'; @@ -84,7 +86,7 @@ int main() { // Create the dynamics Dynamics dynamics{graph, 69, 0.95}; dynamics.setSpeedFluctuationSTD(0.2); - Itinerary itinerary{0, 4}; + Itinerary itinerary{4, 4}; dynamics.addItinerary(itinerary); dynamics.updatePaths(); @@ -108,7 +110,7 @@ int main() { if (progress % 300 == 0) { ofs << progress << ';' << spire.outputCounts(true) << std::endl; } - dynamics.addAgents(0, *it / 2, 0); + dynamics.addAgents(4, *it / 2, 0); } dynamics.evolve(false); ++progress; diff --git a/src/dsm/dsm.hpp b/src/dsm/dsm.hpp index d3cfb1158..3eca5b552 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 = 2; -static constexpr uint8_t DSM_VERSION_PATCH = 5; +static constexpr uint8_t DSM_VERSION_PATCH = 6; static auto const DSM_VERSION = std::format("{}.{}.{}", DSM_VERSION_MAJOR, DSM_VERSION_MINOR, DSM_VERSION_PATCH); diff --git a/src/dsm/headers/Agent.hpp b/src/dsm/headers/Agent.hpp index 3d2ed8be0..3dfe5aaed 100644 --- a/src/dsm/headers/Agent.hpp +++ b/src/dsm/headers/Agent.hpp @@ -42,9 +42,11 @@ namespace dsm { public: /// @brief Construct a new Agent object /// @param id The agent's id - /// @param itineraryId The agent's itinerary + /// @param itineraryId Optional, The agent's destination node. If not provided, the agent is a random agent /// @param srcNodeId Optional, The id of the source node of the agent - Agent(Id id, Id itineraryId, std::optional srcNodeId = std::nullopt); + Agent(Id id, + std::optional itineraryId = std::nullopt, + std::optional srcNodeId = std::nullopt); /// @brief Construct a new Agent object /// @param id The agent's id /// @param itineraryIds The agent's itinerary @@ -123,13 +125,17 @@ namespace dsm { /// @brief Get the agent's travel time /// @return The agent's travel time unsigned int time() const { return m_time; } + /// @brief Return true if the agent is a random agent + /// @return True if the agent is a random agent, false otherwise + bool isRandom() const { return m_trip.empty(); } }; template requires(is_numeric_v) - Agent::Agent(Id id, Id itineraryId, std::optional srcNodeId) + Agent::Agent(Id id, std::optional itineraryId, std::optional srcNodeId) : m_id{id}, - m_trip{itineraryId}, + m_trip{itineraryId.has_value() ? std::vector{itineraryId.value()} + : std::vector{}}, m_srcNodeId{srcNodeId}, m_delay{0}, m_speed{0.}, diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index 4d42b1871..67434f36e 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -193,6 +193,8 @@ namespace dsm { const TContainer& src_weights, const TContainer& dst_weights); + void addRandomAgents(Size nAgents, std::optional srcNodeId = std::nullopt); + /// @brief Remove an agent from the simulation /// @param agentId the id of the agent to remove void removeAgent(Size agentId); @@ -239,6 +241,9 @@ namespace dsm { /// @brief Get the agents /// @return const std::unordered_map>&, The agents const std::map>& agents() const { return m_agents; } + /// @brief Get the number of agents currently in the simulation + /// @return Size The number of agents + const Size nAgents() const { return m_agents.size(); } /// @brief Get the time /// @return Time The time Time time() const { return m_time; } @@ -556,13 +561,28 @@ namespace dsm { } } + template + void Dynamics::addRandomAgents(Size nAgents, std::optional srcNodeId) { + if (m_agents.size() + nAgents > m_graph.maxCapacity()) { + throw std::overflow_error(buildLog( + std::format("Graph is already holding the max possible number of agents ({})", + m_graph.maxCapacity()))); + } + Id agentId{0}; + if (!m_agents.empty()) { + agentId = m_agents.rbegin()->first + 1; + } + for (auto i{0}; i < nAgents; ++i, ++agentId) { + this->addAgent(agent_t{agentId, srcNodeId}); + } + } + template void Dynamics::removeAgent(Size agentId) { m_agents.erase(agentId); } template - template requires(std::is_convertible_v && (std::is_convertible_v && ...)) void Dynamics::removeAgents(T1 id, Tn... ids) { diff --git a/src/dsm/headers/Graph.cpp b/src/dsm/headers/Graph.cpp index 772bd03b9..7eef78dfc 100644 --- a/src/dsm/headers/Graph.cpp +++ b/src/dsm/headers/Graph.cpp @@ -143,7 +143,7 @@ namespace dsm { } } - void Graph::importMatrix(const std::string& fileName, bool isAdj) { + void Graph::importMatrix(const std::string& fileName, bool isAdj, double defaultSpeed) { // check the file extension std::string fileExt = fileName.substr(fileName.find_last_of(".") + 1); if (fileExt == "dsm") { @@ -178,6 +178,7 @@ namespace dsm { if (!isAdj) { m_streets[index]->setLength(val); } + m_streets[index]->setMaxSpeed(defaultSpeed); } } else { // default case: read the file as a matrix with the first two elements being the number of rows and columns and @@ -223,6 +224,7 @@ namespace dsm { if (!isAdj) { m_streets[index]->setLength(value); } + m_streets[index]->setMaxSpeed(defaultSpeed); } ++index; } diff --git a/src/dsm/headers/Graph.hpp b/src/dsm/headers/Graph.hpp index d6dabc945..65e0e62b7 100644 --- a/src/dsm/headers/Graph.hpp +++ b/src/dsm/headers/Graph.hpp @@ -121,9 +121,12 @@ namespace dsm { /// the number of rows and columns and the following elements being the matrix elements. /// @param fileName The name of the file to import the adjacency matrix from. /// @param isAdj A boolean value indicating if the file contains the adjacency matrix or the distance matrix. + /// @param defaultSpeed The default speed limit for the streets /// @throws std::invalid_argument if the file is not found or invalid /// The matrix format is deduced from the file extension. Currently only .dsm files are supported. - void importMatrix(const std::string& fileName, bool isAdj = true); + void importMatrix(const std::string& fileName, + bool isAdj = true, + double defaultSpeed = 13.8888888889); /// @brief Import the graph's nodes from a file /// @param fileName The name of the file to import the nodes from. /// @throws std::invalid_argument if the file is not found, invalid or the format is not supported diff --git a/src/dsm/headers/RoadDynamics.hpp b/src/dsm/headers/RoadDynamics.hpp index fef7414f8..dd76405ee 100644 --- a/src/dsm/headers/RoadDynamics.hpp +++ b/src/dsm/headers/RoadDynamics.hpp @@ -43,7 +43,7 @@ namespace dsm { protected: Time m_previousOptimizationTime; double m_errorProbability; - double m_maxFlowPercentage; + double m_passageProbability; std::vector m_travelTimes; std::unordered_map m_agentNextStreetId; bool m_forcePriorities; @@ -88,11 +88,8 @@ namespace dsm { /// @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); + + void setPassageProbability(double passageProbability); /// @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 @@ -151,7 +148,7 @@ namespace dsm { : Dynamics>(graph, seed), m_previousOptimizationTime{0}, m_errorProbability{0.}, - m_maxFlowPercentage{1.}, + m_passageProbability{1.}, m_forcePriorities{false} { for (const auto& [streetId, street] : this->m_graph.streetSet()) { m_streetTails.emplace(streetId, 0); @@ -183,13 +180,16 @@ namespace dsm { Id RoadDynamics::m_nextStreetId(Id agentId, Id nodeId, std::optional streetId) { + auto const& pAgent{this->m_agents[agentId]}; 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); + 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); + } } } assert(possibleMoves.size() > 0); @@ -233,8 +233,7 @@ namespace dsm { 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()) { + if (pStreet->queue(queueIndex).empty()) { continue; } const auto agentId{pStreet->queue(queueIndex).front()}; @@ -254,8 +253,23 @@ namespace dsm { continue; } } - if (destinationNode->id() == - this->m_itineraries[pAgent->itineraryId()]->destination()) { + auto const bCanPass = uniformDist(this->m_generator) < m_passageProbability; + bool bArrived{false}; + if (!bCanPass) { + if (pAgent->isRandom()) { + m_agentNextStreetId.erase(agentId); + bArrived = true; + } else { + continue; + } + } + if (!pAgent->isRandom()) { + if (destinationNode->id() == + this->m_itineraries[pAgent->itineraryId()]->destination()) { + bArrived = true; + } + } + if (bArrived) { pStreet->dequeue(queueIndex); m_travelTimes.push_back(pAgent->time()); if (reinsert_agents) { @@ -345,6 +359,8 @@ namespace dsm { template requires(is_numeric_v) void RoadDynamics::m_evolveAgents() { + std::uniform_int_distribution nodeDist{ + 0, static_cast(this->m_graph.nNodes() - 1)}; for (const auto& [agentId, agent] : this->m_agents) { if (agent->delay() > 0) { const auto& street{this->m_graph.streetSet()[agent->streetId().value()]}; @@ -361,12 +377,18 @@ namespace dsm { agent->decrementDelay(); if (agent->delay() == 0) { auto const nLanes = street->nLanes(); - if (this->m_itineraries[agent->itineraryId()]->destination() == - street->nodePair().second) { - agent->updateItinerary(); + bool bArrived{false}; + if (!agent->isRandom()) { + if (this->m_itineraries[agent->itineraryId()]->destination() == + street->nodePair().second) { + agent->updateItinerary(); + } + if (this->m_itineraries[agent->itineraryId()]->destination() == + street->nodePair().second) { + bArrived = true; + } } - if (this->m_itineraries[agent->itineraryId()]->destination() == - street->nodePair().second) { + if (bArrived) { std::uniform_int_distribution laneDist{ 0, static_cast(nLanes - 1)}; street->enqueue(agentId, laneDist(this->m_generator)); @@ -398,8 +420,9 @@ namespace dsm { } } 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()]}; + Id srcNodeId = agent->srcNodeId().has_value() ? agent->srcNodeId().value() + : nodeDist(this->m_generator); + const auto& srcNode{this->m_graph.nodeSet()[srcNodeId]}; if (srcNode->isFull()) { continue; } @@ -436,13 +459,12 @@ namespace dsm { 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))); + void RoadDynamics::setPassageProbability(double passageProbability) { + if (passageProbability < 0. || passageProbability > 1.) { + throw std::invalid_argument(buildLog(std::format( + "The passage probability ({}) must be between 0 and 1", passageProbability))); } - m_maxFlowPercentage = maxFlowPercentage; + m_passageProbability = passageProbability; } template diff --git a/test/Test_agent.cpp b/test/Test_agent.cpp index 2ccca267b..b367a8442 100644 --- a/test/Test_agent.cpp +++ b/test/Test_agent.cpp @@ -44,5 +44,12 @@ TEST_CASE("Agent") { } } } + GIVEN("An agent it") { + uint16_t agentId{1}; + WHEN("The agent is constructed") { + auto randomAgent = Agent{agentId}; + THEN("The agent is a random agent") { CHECK(randomAgent.isRandom()); } + } + } } } diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index b81e69bf9..d60df69a0 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -127,7 +127,7 @@ TEST_CASE("Dynamics") { WHEN("We add the agent") { dynamics.addAgent(0, 2); THEN("The agent is added") { - CHECK_EQ(dynamics.agents().size(), 1); + CHECK_EQ(dynamics.nAgents(), 1); const auto& agent = dynamics.agents().at(0); CHECK_EQ(agent->id(), 0); CHECK_EQ(agent->srcNodeId().value(), 0); @@ -163,7 +163,7 @@ TEST_CASE("Dynamics") { THEN( "The number of agents is 1 and the destination is the same as the " "itinerary") { - CHECK_EQ(dynamics.agents().size(), 1); + CHECK_EQ(dynamics.nAgents(), 1); CHECK_EQ(dynamics.itineraries() .at(dynamics.agents().at(0)->itineraryId()) ->destination(), @@ -184,7 +184,7 @@ TEST_CASE("Dynamics") { "The number of agents is 3, the destination and the street is the " "same as " "the itinerary") { - CHECK_EQ(dynamics.agents().size(), 3); + CHECK_EQ(dynamics.nAgents(), 3); CHECK(dynamics.agents().at(0)->streetId().has_value()); CHECK(dynamics.agents().at(1)->streetId().has_value()); CHECK(dynamics.agents().at(2)->streetId().has_value()); @@ -236,7 +236,7 @@ TEST_CASE("Dynamics") { dynamics.addItinerary(Itinerary{0, 2}); dynamics.addAgentsRandomly(1, src, dst); THEN("The agents are correctly set") { - CHECK_EQ(dynamics.agents().size(), 1); + CHECK_EQ(dynamics.nAgents(), 1); CHECK_EQ(dynamics.itineraries() .at(dynamics.agents().at(0)->itineraryId()) ->destination(), @@ -252,7 +252,7 @@ TEST_CASE("Dynamics") { dynamics.addItinerary(Itinerary{2, 107}); dynamics.addAgentsRandomly(3, src, dst); THEN("The agents are correctly set") { - CHECK_EQ(dynamics.agents().size(), 3); + CHECK_EQ(dynamics.nAgents(), 3); CHECK_EQ(dynamics.itineraries() .at(dynamics.agents().at(0)->itineraryId()) ->destination(), @@ -279,6 +279,28 @@ TEST_CASE("Dynamics") { } } } + SUBCASE("addRandomAgents") { + GIVEN("A dynamics object") { + auto const p{0.1}; + auto const n{100}; + auto graph = Graph{}; + graph.importMatrix("./data/matrix.dat", false); + graph.buildAdj(); + graph.normalizeStreetCapacities(); + Dynamics dynamics{graph, 69}; + dynamics.setPassageProbability(p); + WHEN("We add some agent") { + dynamics.addRandomAgents(n); + THEN("The number of agents is correct") { CHECK_EQ(dynamics.nAgents(), 100); } + THEN("If we evolve the dynamics agent disappear gradually") { + for (auto i{0}; i < 40; ++i) { + dynamics.evolve(false); + } + CHECK(dynamics.nAgents() < n); + } + } + } + } SUBCASE("addAgents") { GIVEN("A dynamics object and one itinerary") { auto graph = Graph{}; @@ -296,7 +318,7 @@ TEST_CASE("Dynamics") { THEN( "The number of agents is 1 and the destination is the same as the " "itinerary") { - CHECK_EQ(dynamics.agents().size(), 1); + CHECK_EQ(dynamics.nAgents(), 1); CHECK_EQ(dynamics.itineraries() .at(dynamics.agents().at(0)->itineraryId()) ->destination(), @@ -305,7 +327,7 @@ TEST_CASE("Dynamics") { } WHEN("We add 69 agents with itinerary 0") { dynamics.addAgents(0, 69); - THEN("The number of agents is 69") { CHECK_EQ(dynamics.agents().size(), 69); } + THEN("The number of agents is 69") { CHECK_EQ(dynamics.nAgents(), 69); } } } } @@ -524,7 +546,7 @@ TEST_CASE("Dynamics") { } dynamics.evolve(true); THEN("The agent is reinserted") { - CHECK_EQ(dynamics.agents().size(), 1); + CHECK_EQ(dynamics.nAgents(), 1); CHECK_EQ(dynamics.agents().at(0)->time(), 1); CHECK_EQ(dynamics.agents().at(0)->delay(), 0); CHECK_FALSE(dynamics.agents().at(0)->streetId().has_value());