diff --git a/README.md b/README.md index e1758a64..ce5d2aac 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ cmake --build build -j$(nproc) To run the tests use the command: ```shell -ctest --test-dir build -j$(nproc) +ctest --test-dir build -j$(nproc) --output-on-failure ``` ## Benchmarking diff --git a/src/dsf/bindings.cpp b/src/dsf/bindings.cpp index c8cd0ac6..bb08c1be 100644 --- a/src/dsf/bindings.cpp +++ b/src/dsf/bindings.cpp @@ -482,6 +482,23 @@ PYBIND11_MODULE(dsf_cpp, m) { pybind11::arg("nAgents"), pybind11::arg("itineraryId") = std::nullopt, dsf::g_docstrings.at("dsf::mobility::RoadDynamics::addAgentsUniformly").c_str()) + .def( + "addRandomAgents", + [](dsf::mobility::FirstOrderDynamics& self, std::size_t nAgents) { + self.addRandomAgents(nAgents); + }, + pybind11::arg("nAgents"), + dsf::g_docstrings.at("dsf::mobility::RoadDynamics::addRandomAgents").c_str()) + .def( + "addRandomAgents", + [](dsf::mobility::FirstOrderDynamics& self, + std::size_t nAgents, + const std::unordered_map& src_weights) { + self.addRandomAgents(nAgents, src_weights); + }, + pybind11::arg("nAgents"), + pybind11::arg("src_weights"), + dsf::g_docstrings.at("dsf::mobility::RoadDynamics::addRandomAgents").c_str()) .def( "addAgentsRandomly", [](dsf::mobility::FirstOrderDynamics& self, dsf::Size nAgents) { diff --git a/src/dsf/dsf.hpp b/src/dsf/dsf.hpp index f7b0abd9..cd723225 100644 --- a/src/dsf/dsf.hpp +++ b/src/dsf/dsf.hpp @@ -6,7 +6,7 @@ static constexpr uint8_t DSF_VERSION_MAJOR = 4; static constexpr uint8_t DSF_VERSION_MINOR = 4; -static constexpr uint8_t DSF_VERSION_PATCH = 2; +static constexpr uint8_t DSF_VERSION_PATCH = 3; static auto const DSF_VERSION = std::format("{}.{}.{}", DSF_VERSION_MAJOR, DSF_VERSION_MINOR, DSF_VERSION_PATCH); diff --git a/src/dsf/mobility/Agent.cpp b/src/dsf/mobility/Agent.cpp index 10342c43..585e8d2d 100644 --- a/src/dsf/mobility/Agent.cpp +++ b/src/dsf/mobility/Agent.cpp @@ -32,15 +32,21 @@ namespace dsf::mobility { void Agent::setSrcNodeId(Id srcNodeId) { m_srcNodeId = srcNodeId; } void Agent::setStreetId(std::optional streetId) { if (!streetId.has_value()) { - assert(m_nextStreetId.has_value()); + if (!m_nextStreetId.has_value()) { + throw std::logic_error(std::format( + "Agent {} has no next street id to set the current street id to", m_id)); + } m_streetId = std::exchange(m_nextStreetId, std::nullopt); return; } - assert(m_nextStreetId.has_value() ? streetId == m_nextStreetId.value() : true); + if (m_nextStreetId.has_value()) { + throw std::logic_error(std::format( + "Agent {} has a next street id already set, cannot set street id directly", + m_id)); + } m_streetId = streetId; m_nextStreetId = std::nullopt; } - void Agent::setNextStreetId(Id nextStreetId) { m_nextStreetId = nextStreetId; } void Agent::setSpeed(double speed) { if (speed < 0.) { throw std::invalid_argument( diff --git a/src/dsf/mobility/Agent.hpp b/src/dsf/mobility/Agent.hpp index 0b0bcad2..ea746dbb 100644 --- a/src/dsf/mobility/Agent.hpp +++ b/src/dsf/mobility/Agent.hpp @@ -58,7 +58,7 @@ namespace dsf::mobility { void setStreetId(std::optional streetId = std::nullopt); /// @brief Set the id of the next street /// @param nextStreetId The id of the next street - void setNextStreetId(Id nextStreetId); + inline auto setNextStreetId(Id nextStreetId) { m_nextStreetId = nextStreetId; } /// @brief Set the agent's speed /// @param speed, The agent's speed /// @throw std::invalid_argument, if speed is negative @@ -127,8 +127,6 @@ struct std::formatter { constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); } template auto format(const dsf::mobility::Agent& agent, FormatContext&& ctx) const { - auto const strItinerary = agent.trip().empty() ? std::string("RANDOM") - : std::to_string(agent.itineraryId()); return std::format_to( ctx.out(), "Agent(id: {}, streetId: {}, srcNodeId: {}, nextStreetId: {}, " @@ -139,7 +137,7 @@ struct std::formatter { agent.srcNodeId().has_value() ? std::to_string(agent.srcNodeId().value()) : "N/A", agent.nextStreetId().has_value() ? std::to_string(agent.nextStreetId().value()) : "N/A", - strItinerary, + agent.isRandom() ? std::string("RANDOM") : std::to_string(agent.itineraryId()), agent.speed(), agent.distance(), agent.spawnTime(), diff --git a/src/dsf/mobility/FirstOrderDynamics.hpp b/src/dsf/mobility/FirstOrderDynamics.hpp index cbf2f73c..629d63cb 100644 --- a/src/dsf/mobility/FirstOrderDynamics.hpp +++ b/src/dsf/mobility/FirstOrderDynamics.hpp @@ -7,10 +7,9 @@ namespace dsf::mobility { double m_alpha; double m_speedFluctuationSTD; - double m_speedFactor(double const& density) const; + double m_speedFactor(double const& density) const final; - double m_streetEstimatedTravelTime( - std::unique_ptr const& pStreet) const override; + double m_streetEstimatedTravelTime(std::unique_ptr const& pStreet) const final; public: /// @brief Construct a new First Order Dynamics object diff --git a/src/dsf/mobility/RoadDynamics.hpp b/src/dsf/mobility/RoadDynamics.hpp index 1ab6647a..1b268aa2 100644 --- a/src/dsf/mobility/RoadDynamics.hpp +++ b/src/dsf/mobility/RoadDynamics.hpp @@ -35,7 +35,8 @@ #include "RoadNetwork.hpp" #include "../utility/Typedef.hpp" -static auto constexpr g_cacheFolder = "./.dsfcache/"; +static constexpr auto CACHE_FOLDER = "./.dsfcache/"; +static constexpr auto U_TURN_PENALTY_FACTOR = 0.1; namespace dsf::mobility { /// @brief The RoadDynamics class represents the dynamics of the network. @@ -83,9 +84,9 @@ namespace dsf::mobility { /// @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(std::unique_ptr const& pAgent, - Id NodeId, - std::optional streetId = std::nullopt); + std::optional m_nextStreetId(std::unique_ptr const& pAgent, + Id NodeId, + std::optional streetId = std::nullopt); /// @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 @@ -103,6 +104,7 @@ namespace dsf::mobility { void m_trafficlightSingleTailOptimizer(double const& beta, std::optional& logStream); + virtual double m_speedFactor(double const& density) const = 0; virtual double m_streetEstimatedTravelTime( std::unique_ptr const& pStreet) const = 0; @@ -166,8 +168,11 @@ namespace dsf::mobility { inline void setMaxTravelTime(std::time_t const maxTravelTime) noexcept { m_maxTravelTime = maxTravelTime; }; + /// @brief Set the origin nodes + /// @param originNodes The origin nodes void setOriginNodes(std::unordered_map const& originNodes); - + /// @brief Set the destination nodes + /// @param destinationNodes The destination nodes void setDestinationNodes(std::unordered_map const& destinationNodes); /// @brief Set the destination nodes /// @param destinationNodes The destination nodes (as an initializer list) @@ -194,6 +199,13 @@ namespace dsf::mobility { /// @param itineraryId The id of the itinerary to use (default is std::nullopt) /// @throw std::runtime_error If there are no itineraries void addAgentsUniformly(Size nAgents, std::optional itineraryId = std::nullopt); + + template + requires(std::is_same_v> || + std::is_same_v>) + void addRandomAgents(std::size_t nAgents, TContainer const& spawnWeights); + + void addRandomAgents(std::size_t nAgents); /// @brief Add a set of agents to the simulation /// @param nAgents The number of agents to add /// @param src_weights The weights of the source nodes @@ -392,10 +404,10 @@ namespace dsf::mobility { m_forcePriorities{false} { this->setWeightFunction(weightFunction, weightTreshold); if (m_bCacheEnabled) { - if (!std::filesystem::exists(g_cacheFolder)) { - std::filesystem::create_directory(g_cacheFolder); + if (!std::filesystem::exists(CACHE_FOLDER)) { + std::filesystem::create_directory(CACHE_FOLDER); } - spdlog::info("Cache enabled (default folder is {})", g_cacheFolder); + spdlog::info("Cache enabled (default folder is {})", CACHE_FOLDER); } for (auto const& [nodeId, pNode] : this->graph().nodes()) { m_nodeIndices.push_back(nodeId); @@ -436,10 +448,11 @@ namespace dsf::mobility { template requires(is_numeric_v) - std::unique_ptr RoadDynamics::m_killAgent(std::unique_ptr pAgent) { + std::unique_ptr RoadDynamics::m_killAgent( + std::unique_ptr pAgent) { spdlog::trace("Killing agent {}", *pAgent); - m_travelDTs.push_back( - {pAgent->distance(), static_cast(this->time_step() - pAgent->spawnTime())}); + m_travelDTs.push_back({pAgent->distance(), + static_cast(this->time_step() - pAgent->spawnTime())}); --m_nAgents; return std::move(pAgent); } @@ -448,7 +461,7 @@ namespace dsf::mobility { requires(is_numeric_v) void RoadDynamics::m_updatePath(std::unique_ptr const& pItinerary) { if (m_bCacheEnabled) { - auto const& file = std::format("{}{}.ity", g_cacheFolder, pItinerary->id()); + auto const& file = std::format("{}{}.ity", CACHE_FOLDER, pItinerary->id()); if (std::filesystem::exists(file)) { pItinerary->load(file); spdlog::debug("Loaded cached path for itinerary {}", pItinerary->id()); @@ -474,18 +487,67 @@ namespace dsf::mobility { newSize); } if (m_bCacheEnabled) { - pItinerary->save(std::format("{}{}.ity", g_cacheFolder, pItinerary->id())); + pItinerary->save(std::format("{}{}.ity", CACHE_FOLDER, pItinerary->id())); spdlog::debug("Saved path in cache for itinerary {}", pItinerary->id()); } } template requires(is_numeric_v) - Id RoadDynamics::m_nextStreetId(std::unique_ptr const& pAgent, - Id nodeId, - std::optional streetId) { + std::optional RoadDynamics::m_nextStreetId( + std::unique_ptr const& pAgent, Id nodeId, std::optional streetId) { // Get outgoing edges directly - avoid storing targets separately - const auto& outgoingEdges = this->graph().node(nodeId)->outgoingEdges(); + auto const& pNode{this->graph().node(nodeId)}; + auto const& outgoingEdges = pNode->outgoingEdges(); + if (outgoingEdges.size() == 1) { + return outgoingEdges[0]; + } + if (pAgent->isRandom()) { // Try to use street transition probabilities + spdlog::trace("Computing m_nextStreetId for {}", *pAgent); + if (streetId.has_value()) { + auto const& pStreetCurrent{this->graph().edge(streetId.value())}; + auto const speedCurrent{pStreetCurrent->maxSpeed() /** + this->m_speedFactor(pStreetCurrent->density())*/}; + double cumulativeProbability = 0.0; + std::unordered_map transitionProbabilities; + for (const auto outEdgeId : outgoingEdges) { + auto const& pStreetOut{this->graph().edge(outEdgeId)}; + auto const speed{pStreetOut->maxSpeed() /** + this->m_speedFactor(pStreetOut->density())*/}; + double probability = speed * speedCurrent; + if (pStreetOut->target() == pStreetCurrent->source()) { + if (pNode->isRoundabout()) { + probability *= U_TURN_PENALTY_FACTOR; // Discourage U-TURNS + } else { + continue; // No U-TURNS + } + } + transitionProbabilities[pStreetOut->id()] = probability; + cumulativeProbability += probability; + } + std::uniform_real_distribution uniformDist{0., cumulativeProbability}; + auto const randValue = uniformDist(this->m_generator); + cumulativeProbability = 0.0; + for (const auto& [targetStreetId, probability] : transitionProbabilities) { + cumulativeProbability += probability; + if (randValue <= cumulativeProbability) { + return targetStreetId; + } + } + // auto const& transitionProbabilities = pStreetCurrent->transitionProbabilities(); + // if (!transitionProbabilities.empty()) { + // std::uniform_real_distribution uniformDist{0., 1.}; + // auto const randValue = uniformDist(this->m_generator); + // double cumulativeProbability = 0.0; + // for (const auto& [targetStreetId, probability] : transitionProbabilities) { + // cumulativeProbability += probability; + // if (randValue <= cumulativeProbability) { + // return targetStreetId; + // } + // } + // } + } + } std::vector possibleEdgeIds; possibleEdgeIds.reserve(outgoingEdges.size()); // Pre-allocate to avoid reallocations @@ -516,7 +578,7 @@ namespace dsf::mobility { std::unordered_set allowedTargets; bool hasItineraryConstraints = false; - if (!pAgent->isRandom() && !this->itineraries().empty()) { + if (!this->itineraries().empty()) { std::uniform_real_distribution uniformDist{0., 1.}; if (!(m_errorProbability.has_value() && uniformDist(this->m_generator) < m_errorProbability)) { @@ -561,8 +623,9 @@ namespace dsf::mobility { } if (possibleEdgeIds.empty()) { - throw std::runtime_error( - std::format("No possible moves for agent {} at node {}.", *pAgent, nodeId)); + return std::nullopt; + // throw std::runtime_error( + // std::format("No possible moves for agent {} at node {}.", *pAgent, nodeId)); } if (possibleEdgeIds.size() == 1) { @@ -579,6 +642,7 @@ namespace dsf::mobility { void RoadDynamics::m_evolveStreet(const std::unique_ptr& pStreet, bool reinsert_agents) { auto const nLanes = pStreet->nLanes(); + // Enqueue moving agents if their free time is up while (!pStreet->movingAgents().empty()) { auto const& pAgent{pStreet->movingAgents().top()}; if (pAgent->freeTime() < this->time_step()) { @@ -604,8 +668,20 @@ namespace dsf::mobility { } auto const nextStreetId = this->m_nextStreetId(pAgent, pStreet->target(), pStreet->id()); - auto const& pNextStreet{this->graph().edge(nextStreetId)}; - pAgent->setNextStreetId(nextStreetId); + if (!nextStreetId.has_value()) { + spdlog::debug( + "No next street found for agent {} at node {}", *pAgent, pStreet->target()); + if (pAgent->isRandom()) { + std::uniform_int_distribution laneDist{0, + static_cast(nLanes - 1)}; + pStreet->enqueue(laneDist(this->m_generator)); + continue; + } + throw std::runtime_error(std::format( + "No next street found for agent {} at node {}", *pAgent, pStreet->target())); + } + auto const& pNextStreet{this->graph().edge(nextStreetId.value())}; + pAgent->setNextStreetId(pNextStreet->id()); if (nLanes == 1) { pStreet->enqueue(0); continue; @@ -691,7 +767,7 @@ namespace dsf::mobility { timeTolerance, timeDiff); // Kill the agent - auto pAgent{pStreet->dequeue(queueIndex)}; + this->m_killAgent(std::move(pStreet->dequeue(queueIndex))); continue; } } @@ -837,16 +913,20 @@ namespace dsf::mobility { destinationNode->id()); } } else { - if (pAgentTemp->distance() >= m_maxTravelDistance) { + if (!pAgentTemp->nextStreetId().has_value()) { bArrived = true; - } - if (!bArrived && - (this->time_step() - pAgentTemp->spawnTime() >= m_maxTravelTime)) { + spdlog::debug("Random agent {} has arrived at destination node {}", + pAgentTemp->id(), + destinationNode->id()); + } else if (pAgentTemp->distance() >= m_maxTravelDistance) { + bArrived = true; + } else if (!bArrived && + (this->time_step() - pAgentTemp->spawnTime() >= m_maxTravelTime)) { bArrived = true; } } if (bArrived) { - auto pAgent = this->m_killAgent(pStreet->dequeue(queueIndex)); + auto pAgent = this->m_killAgent(std::move(pStreet->dequeue(queueIndex))); if (reinsert_agents) { // reset Agent's values pAgent->reset(this->time_step()); @@ -868,6 +948,14 @@ namespace dsf::mobility { continue; } auto pAgent{pStreet->dequeue(queueIndex)}; + spdlog::debug( + "{} at time {} has been dequeued from street {} and enqueued on street {} " + "with free time {}.", + *pAgent, + this->time_step(), + pStreet->id(), + nextStreet->id(), + pAgent->freeTime()); assert(destinationNode->id() == nextStreet->source()); if (destinationNode->isIntersection()) { auto& intersection = dynamic_cast(*destinationNode); @@ -985,8 +1073,15 @@ namespace dsf::mobility { } if (!pAgent->nextStreetId().has_value()) { spdlog::debug("No next street id, generating a random one"); - pAgent->setNextStreetId( - this->m_nextStreetId(pAgent, pSourceNode->id(), pAgent->streetId())); + auto const nextStreetId{ + this->m_nextStreetId(pAgent, pSourceNode->id(), pAgent->streetId())}; + if (!nextStreetId.has_value()) { + spdlog::debug( + "No next street found for agent {} at node {}", *pAgent, pSourceNode->id()); + itAgent = m_agents.erase(itAgent); + continue; + } + pAgent->setNextStreetId(nextStreetId.value()); } // spdlog::debug("Checking next street {}", pAgent->nextStreetId().value()); auto const& nextStreet{ @@ -1251,6 +1346,40 @@ namespace dsf::mobility { } } + template + requires(is_numeric_v) + template + requires(std::is_same_v> || + std::is_same_v>) + void RoadDynamics::addRandomAgents(std::size_t nAgents, + TContainer const& spawnWeights) { + std::uniform_real_distribution uniformDist{0., 1.}; + auto const bUniformSpawn{spawnWeights.empty()}; + auto const bSingleSource{spawnWeights.size() == 1}; + while (nAgents--) { + if (bUniformSpawn) { + this->addAgent(); + } else if (bSingleSource) { + this->addAgent(std::nullopt, spawnWeights.begin()->first); + } else { + auto const randValue{uniformDist(this->m_generator)}; + double cumulativeWeight{0.}; + for (auto const& [spawnNodeId, weight] : spawnWeights) { + cumulativeWeight += weight; + if (randValue <= cumulativeWeight) { + this->addAgent(std::nullopt, spawnNodeId); + break; + } + } + } + } + } + + template + requires(is_numeric_v) + void RoadDynamics::addRandomAgents(std::size_t nAgents) { + addRandomAgents(nAgents, this->m_originNodes); + } template requires(is_numeric_v) template diff --git a/src/dsf/mobility/RoadNetwork.cpp b/src/dsf/mobility/RoadNetwork.cpp index 18edfb1c..b7671a7d 100644 --- a/src/dsf/mobility/RoadNetwork.cpp +++ b/src/dsf/mobility/RoadNetwork.cpp @@ -170,7 +170,7 @@ namespace dsf::mobility { } } } - void RoadNetwork::m_jsonEdgesImporter(std::ifstream& file, const bool bCreateInverse) { + void RoadNetwork::m_jsonEdgesImporter(std::ifstream& file) { // Read the file into a string std::string json_str((std::istreambuf_iterator(file)), std::istreambuf_iterator()); @@ -239,44 +239,40 @@ namespace dsf::mobility { name = std::string(edge_properties["name"].get_string().value()); } - if (!bCreateInverse) { - addStreet(Street(edge_id, - std::make_pair(src_node_id, dst_node_id), - edge_length, - edge_maxspeed, - edge_lanes, - name, - geometry)); - // Check if there is coilcode property - if (!edge_properties["coilcode"].is_null()) { - if (edge_properties["coilcode"].is_string()) { - std::string strCoilCode{edge_properties["coilcode"].get_string().value()}; - addCoil(edge_id, strCoilCode); - } else if (edge_properties["coilcode"].is_number()) { - std::string strCoilCode = - std::to_string(edge_properties["coilcode"].get_uint64()); - addCoil(edge_id, strCoilCode); - } else { - spdlog::warn("Invalid coilcode for edge {}, adding default", edge_id); - addCoil(edge_id); - } + addStreet(Street(edge_id, + std::make_pair(src_node_id, dst_node_id), + edge_length, + edge_maxspeed, + edge_lanes, + name, + geometry)); + // Check if there is coilcode property + if (!edge_properties.at_key("coilcode").error() && + edge_properties["coilcode"].has_value()) { + if (edge_properties["coilcode"].is_string()) { + std::string strCoilCode{edge_properties["coilcode"].get_string().value()}; + addCoil(edge_id, strCoilCode); + } else if (edge_properties["coilcode"].is_number()) { + std::string strCoilCode = + std::to_string(edge_properties["coilcode"].get_uint64()); + addCoil(edge_id, strCoilCode); + } else { + spdlog::warn("Invalid coilcode for edge {}, adding default", edge_id); + addCoil(edge_id); } - } else { - addStreet(Street(edge_id * 10, - std::make_pair(src_node_id, dst_node_id), - edge_length, - edge_maxspeed, - edge_lanes, - name, - geometry)); - addStreet(Street(edge_id * 10 + 1, - std::make_pair(dst_node_id, src_node_id), - edge_length, - edge_maxspeed, - edge_lanes, - name, - geometry::PolyLine(geometry.rbegin(), geometry.rend()))); } + // Check for transition probabilities + // if (!edge_properties.at_key("transition_probabilities").error() && + // edge_properties["transition_probabilities"].has_value()) { + // auto const& tp = edge_properties["transition_probabilities"]; + // std::unordered_map transitionProbabilities; + // for (auto const& [key, value] : tp.get_object()) { + // auto const targetStreetId = static_cast(std::stoull(std::string(key))); + // auto const probability = static_cast(value.get_double()); + // transitionProbabilities.emplace(targetStreetId, probability); + // } + // edge(edge_id)->setTransitionProbabilities(transitionProbabilities); + // } } this->m_nodes.rehash(0); this->m_edges.rehash(0); diff --git a/src/dsf/mobility/RoadNetwork.hpp b/src/dsf/mobility/RoadNetwork.hpp index 3e730a7f..d23af7d1 100644 --- a/src/dsf/mobility/RoadNetwork.hpp +++ b/src/dsf/mobility/RoadNetwork.hpp @@ -58,7 +58,7 @@ namespace dsf::mobility { void m_csvEdgesImporter(std::ifstream& file, const char separator = ';'); void m_csvNodePropertiesImporter(std::ifstream& file, const char separator = ';'); - void m_jsonEdgesImporter(std::ifstream& file, const bool bCreateInverse = false); + void m_jsonEdgesImporter(std::ifstream& file); public: RoadNetwork(); @@ -245,7 +245,7 @@ namespace dsf::mobility { case FileExt::GEOJSON: case FileExt::JSON: spdlog::debug("Importing nodes from JSON file: {}", fileName); - this->m_jsonEdgesImporter(file, std::forward(args)...); + this->m_jsonEdgesImporter(file); break; default: throw std::invalid_argument( diff --git a/src/dsf/mobility/Street.hpp b/src/dsf/mobility/Street.hpp index 0aeda272..96ecb58d 100644 --- a/src/dsf/mobility/Street.hpp +++ b/src/dsf/mobility/Street.hpp @@ -48,6 +48,7 @@ namespace dsf::mobility { AgentComparator> m_movingAgents; std::vector m_laneMapping; + // std::unordered_map m_transitionProbabilities; std::optional m_counter; public: @@ -83,6 +84,12 @@ namespace dsf::mobility { /// @param meanVehicleLength The mean vehicle length /// @throw std::invalid_argument If the mean vehicle length is negative static void setMeanVehicleLength(double meanVehicleLength); + // /// @brief Set the street's transition probabilities + // /// @param transitionProbabilities The street's transition probabilities + // inline void setTransitionProbabilities( + // std::unordered_map const& transitionProbabilities) { + // m_transitionProbabilities = transitionProbabilities; + // }; /// @brief Enable a coil (dsf::Counter sensor) on the street /// @param name The name of the counter (default is "Coil_") void enableCounter(std::string name = std::string()); @@ -110,6 +117,10 @@ namespace dsf::mobility { /// @brief Check if the street is full /// @return bool, True if the street is full, false otherwise inline bool isFull() const final { return this->nAgents() == this->m_capacity; } + + // inline auto const& transitionProbabilities() const { + // return m_transitionProbabilities; + // } /// @brief Get the name of the counter /// @return std::string The name of the counter inline auto counterName() const { diff --git a/test/mobility/Test_agent.cpp b/test/mobility/Test_agent.cpp index 068eb612..da1377ee 100644 --- a/test/mobility/Test_agent.cpp +++ b/test/mobility/Test_agent.cpp @@ -65,14 +65,15 @@ TEST_CASE("Agent methods") { } SUBCASE("setStreetId with value") { agent.setNextStreetId(55); - agent.setStreetId(55); - CHECK(agent.streetId().has_value()); + CHECK_THROWS_AS(agent.setStreetId(55), std::logic_error); + CHECK_FALSE(agent.streetId().has_value()); + agent.setStreetId(); CHECK_EQ(agent.streetId().value(), 55); CHECK_FALSE(agent.nextStreetId().has_value()); } SUBCASE("setStreetId with nullopt uses nextStreetId") { agent.setNextStreetId(77); - agent.setStreetId(std::nullopt); + agent.setStreetId(); CHECK(agent.streetId().has_value()); CHECK_EQ(agent.streetId().value(), 77); } @@ -104,7 +105,6 @@ TEST_CASE("Agent methods") { agent.setSpeed(10.); agent.incrementDistance(5.); agent.setFreeTime(99); - agent.setNextStreetId(88); agent.setStreetId(88); agent.updateItinerary(); agent.reset(555); diff --git a/test/mobility/Test_dynamics.cpp b/test/mobility/Test_dynamics.cpp index 1705fade..1909986d 100644 --- a/test/mobility/Test_dynamics.cpp +++ b/test/mobility/Test_dynamics.cpp @@ -1162,4 +1162,99 @@ TEST_CASE("FirstOrderDynamics") { } } } + SUBCASE("Transition probabilities") { + GIVEN("A simple network with a Y-junction") { + // Create a network with one incoming street and two outgoing streets + // Street layout: + // 0 --[s0]--> 1 --[s1]--> 2 + // \--[s2]--> 3 + Street s0{0, std::make_pair(0, 1), 300., 50.}; + Street s1{1, std::make_pair(1, 2), 1000., 50.}; + Street s2{2, std::make_pair(1, 3), 1000., 30.}; + + RoadNetwork graph; + graph.addStreets(s0, s1, s2); + graph.autoMapStreetLanes(); + graph.adjustNodeCapacities(); + + FirstOrderDynamics dynamics{graph, false, 42, 0., dsf::PathWeight::LENGTH}; + dynamics.setOriginNodes({{0, 1.0}}); + + WHEN("We add multiple random agents and evolve the system") { + // spdlog::set_level(spdlog::level::debug); + dynamics.addRandomAgents(6); + CHECK_EQ(dynamics.nAgents(), 6); + // Evolve to get agents onto street 0 + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + CHECK_EQ(dynamics.graph().edge(0)->nAgents(), 6); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + + THEN("The distribution of agents follows the transition probabilities") { + CHECK_EQ(dynamics.graph().edge(0)->nAgents(), 0); + CHECK_EQ(dynamics.graph().edge(1)->nAgents(), 4); + CHECK_EQ(dynamics.graph().edge(2)->nAgents(), 2); + } + // spdlog::set_level(spdlog::level::info); + } + } + + GIVEN("A network with three possible exits from a node") { + // Create a network with one incoming street and three outgoing streets + // Street layout: + // 0 --[s0]--> 1 --[s1]--> 2 + // |--[s2]--> 3 + // \--[s3]--> 4 + Street s0{0, std::make_pair(0, 1), 300., 50.}; + Street s1{1, std::make_pair(1, 2), 1000., 50.}; + Street s2{2, std::make_pair(1, 3), 1000., 40.}; + Street s3{3, std::make_pair(1, 4), 1000., 30.}; + + RoadNetwork graph; + graph.addStreets(s0, s1, s2, s3); + graph.autoMapStreetLanes(); + graph.adjustNodeCapacities(); + + FirstOrderDynamics dynamics{graph, false, 123, 0., dsf::PathWeight::LENGTH}; + dynamics.setOriginNodes({{0, 1.0}}); + + WHEN("We add multiple random agents and evolve the system") { + dynamics.addRandomAgents(6); + CHECK_EQ(dynamics.nAgents(), 6); + // Evolve to get agents onto street 0 + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + CHECK_EQ(dynamics.graph().edge(0)->nAgents(), 6); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + dynamics.evolve(false); + + THEN("The distribution of agents follows the transition probabilities") { + CHECK_EQ(dynamics.graph().edge(0)->nAgents(), 0); + CHECK_EQ(dynamics.graph().edge(1)->nAgents(), 2); + CHECK_EQ(dynamics.graph().edge(2)->nAgents(), 2); + CHECK_EQ(dynamics.graph().edge(3)->nAgents(), 2); + } + } + } + } }