diff --git a/src/dsm/dsm.hpp b/src/dsm/dsm.hpp index 7a9cb2f8a..188a70ee6 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 = 4; -static constexpr uint8_t DSM_VERSION_PATCH = 0; +static constexpr uint8_t DSM_VERSION_PATCH = 1; static auto const DSM_VERSION = std::format("{}.{}.{}", DSM_VERSION_MAJOR, DSM_VERSION_MINOR, DSM_VERSION_PATCH); diff --git a/src/dsm/headers/DijkstraWeights.hpp b/src/dsm/headers/DijkstraWeights.hpp index 89639ec4f..1184e5f86 100644 --- a/src/dsm/headers/DijkstraWeights.hpp +++ b/src/dsm/headers/DijkstraWeights.hpp @@ -1,4 +1,3 @@ - #pragma once #include "../utility/Typedef.hpp" @@ -7,7 +6,20 @@ namespace dsm { class Graph; - double streetLength(const Graph* graph, Id node1, Id node2); - double streetTime(const Graph* graph, Id node1, Id node2); + namespace weight_functions { + /// @brief Returns the length of a street given its source and destination nodes + /// @param graph A pointer to the graph + /// @param node1 The source node id + /// @param node2 The destination node id + /// @return The length of the street + double streetLength(const Graph* graph, Id node1, Id node2); + /// @brief Returns the time to cross a street given its source and destination nodes + /// @param graph A pointer to the graph + /// @param node1 The source node id + /// @param node2 The destination node id + /// @return The time to cross the street + /// @details This time also takes into account the number of agents on the street + double streetTime(const Graph* graph, Id node1, Id node2); + } // namespace weight_functions }; // namespace dsm diff --git a/src/dsm/headers/Dynamics.hpp b/src/dsm/headers/Dynamics.hpp index cebcb5416..b3028d39d 100644 --- a/src/dsm/headers/Dynamics.hpp +++ b/src/dsm/headers/Dynamics.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "DijkstraWeights.hpp" @@ -72,6 +73,8 @@ namespace dsm { private: std::map> m_agents; std::unordered_map> m_itineraries; + std::function m_weightFunction; + double m_weightTreshold; bool m_bCacheEnabled; protected: @@ -97,7 +100,6 @@ namespace dsm { } } - auto const dimension = static_cast(m_graph.nNodes()); auto const destinationID = pItinerary->destination(); std::vector shortestDistances(m_graph.nNodes()); tbb::parallel_for_each( @@ -108,7 +110,7 @@ namespace dsm { if (nodeId == destinationID) { shortestDistances[nodeId] = -1.; } else { - auto result = m_graph.shortestPath(nodeId, destinationID); + auto result = m_graph.shortestPath(nodeId, destinationID, m_weightFunction); if (result.has_value()) { shortestDistances[nodeId] = result.value().distance(); } else { @@ -132,17 +134,17 @@ namespace dsm { auto const& row{m_graph.adjMatrix().getRow(nodeId)}; for (const auto nextNodeId : row) { if (nextNodeId == destinationID) { - if (std::abs(m_graph.street(nodeId * dimension + nextNodeId)->length() - - minDistance) < 1.) // 1 meter tolerance between shortest paths + if (std::abs(m_weightFunction(&m_graph, nodeId, nextNodeId) - minDistance) < + m_weightTreshold) // 1 meter tolerance between shortest paths { path.insert(nodeId, nextNodeId); } else { Logger::debug( std::format("Found a path from {} to {} which differs for more than {} " - "meter(s) from the shortest one.", + "unit(s) from the shortest one.", nodeId, destinationID, - 1.)); + m_weightTreshold)); } continue; } @@ -151,18 +153,18 @@ namespace dsm { continue; } bool const bIsMinDistance{ - std::abs(m_graph.street(nodeId * dimension + nextNodeId)->length() + - distance - minDistance) < - 1.}; // 1 meter tolerance between shortest paths + std::abs(m_weightFunction(&m_graph, nodeId, nextNodeId) + distance - + minDistance) < + m_weightTreshold}; // 1 meter tolerance between shortest paths if (bIsMinDistance) { path.insert(nodeId, nextNodeId); } else { Logger::debug( std::format("Found a path from {} to {} which differs for more than {} " - "meter(s) from the shortest one.", + "unit(s) from the shortest one.", nodeId, destinationID, - 1.)); + m_weightTreshold)); } } } @@ -199,6 +201,19 @@ namespace dsm { /// @brief Update the paths of the itineraries based on the actual travel times virtual void updatePaths(); + /// @brief Set the weight function for the Dijkstra's algorithm + /// @param weightFunction A std::function returning a double value and taking as arguments a + /// pointer to the graph, an id of a source node and an id of a target node (for the edge) + /// @details The weight function must return the weight of the edge between the source and the + /// target node. One can use the predefined weight functions in the DijkstraWeights.hpp file, + /// like weight_functions::streetLength or weight_functions::streetTime. + void setWeightFunction(std::function weightFunction); + /// @brief Set the weight treshold for updating the paths + /// @param weightTreshold The weight treshold + /// @details If two paths differs only for a weight smaller than the treshold, the two paths are + /// considered equivalent. + void setWeightTreshold(double weightTreshold) { m_weightTreshold = weightTreshold; } + /// @brief Set the destination nodes /// @param destinationNodes The destination nodes (as an initializer list) /// @param updatePaths If true, the paths are updated @@ -343,7 +358,9 @@ namespace dsm { Dynamics::Dynamics(Graph& graph, bool useCache, std::optional seed) - : m_bCacheEnabled{useCache}, + : m_weightFunction{weight_functions::streetLength}, + m_weightTreshold{1.}, + m_bCacheEnabled{useCache}, m_graph{std::move(graph)}, m_time{0}, m_previousSpireTime{0}, @@ -371,6 +388,12 @@ namespace dsm { }); } + template + void Dynamics::setWeightFunction( + std::function weightFunction) { + m_weightFunction = weightFunction; + } + template void Dynamics::setDestinationNodes(std::initializer_list destinationNodes, bool updatePaths) { diff --git a/src/dsm/headers/Graph.hpp b/src/dsm/headers/Graph.hpp index b650d2738..dd74a2194 100644 --- a/src/dsm/headers/Graph.hpp +++ b/src/dsm/headers/Graph.hpp @@ -276,9 +276,10 @@ namespace dsm { /// @return A DijkstraResult object containing the path and the distance template > requires(std::is_same_v, double>) - std::optional shortestPath(const Node& source, - const Node& destination, - Func f = streetLength) const; + std::optional shortestPath( + const Node& source, + const Node& destination, + Func f = weight_functions::streetLength) const; /// @brief Get the shortest path between two nodes using dijkstra algorithm /// @param source The source node id @@ -286,9 +287,8 @@ namespace dsm { /// @return A DijkstraResult object containing the path and the distance template > requires(std::is_same_v, double>) - std::optional shortestPath(Id source, - Id destination, - Func f = streetLength) const; + std::optional shortestPath( + Id source, Id destination, Func f = weight_functions::streetLength) const; }; template diff --git a/src/dsm/sources/DijkstraWeights.cpp b/src/dsm/sources/DijkstraWeights.cpp index fed2f8697..71bb54d40 100644 --- a/src/dsm/sources/DijkstraWeights.cpp +++ b/src/dsm/sources/DijkstraWeights.cpp @@ -4,18 +4,19 @@ namespace dsm { - double streetLength(const Graph* graph, Id node1, Id node2) { - const auto street{graph->street(node1, node2)}; - return (*street)->length(); - } + namespace weight_functions { + double streetLength(const Graph* graph, Id node1, Id node2) { + const auto street{graph->street(node1, node2)}; + return (*street)->length(); + } - double streetTime(const Graph* graph, Id node1, Id node2) { - const auto street{graph->street(node1, node2)}; - const auto length{(*street)->length()}; - const auto speed{(*street)->maxSpeed() * - (1. - (*street)->nAgents() / (*street)->capacity())}; - - return length / speed; - } + double streetTime(const Graph* graph, Id node1, Id node2) { + const auto street{graph->street(node1, node2)}; + const auto length{(*street)->length()}; + const auto speed{(*street)->maxSpeed() * + (1. - (*street)->nAgents() / (*street)->capacity())}; + return length / speed; + } + } // namespace weight_functions }; // namespace dsm diff --git a/test/Test_dynamics.cpp b/test/Test_dynamics.cpp index 1f0cdd318..1b1e8071a 100644 --- a/test/Test_dynamics.cpp +++ b/test/Test_dynamics.cpp @@ -376,7 +376,43 @@ TEST_CASE("Dynamics") { graph.addStreets(s1, s2, s3, s4); graph.buildAdj(); Dynamics dynamics{graph, false, 69}; - dynamics.addItinerary(std::unique_ptr(new Itinerary(0, 2))); + dynamics.addItinerary(0, 2); + WHEN("We update the paths") { + dynamics.updatePaths(); + THEN("The path is updated and correctly formed") { + CHECK_EQ(dynamics.itineraries().size(), 1); + CHECK_EQ(dynamics.itineraries().at(0)->path()->size(), 4); + CHECK_EQ(dynamics.itineraries().at(0)->path()->n(), 4); + CHECK(dynamics.itineraries().at(0)->path()->operator()(0, 1)); + CHECK(dynamics.itineraries().at(0)->path()->operator()(1, 2)); + CHECK(dynamics.itineraries().at(0)->path()->operator()(0, 3)); + CHECK(dynamics.itineraries().at(0)->path()->operator()(3, 2)); + for (auto const& it : dynamics.itineraries()) { + auto const& path = it.second->path(); + for (uint16_t i{0}; i < path->n(); ++i) { + if (i == it.second->destination()) { + CHECK_FALSE(path->getRow(i).size()); + } else { + CHECK(path->getRow(i).size()); + } + } + } + } + } + } + GIVEN( + "A dynamics objects, many streets and an itinerary with bifurcations (TIME " + "WEIGHTED)") { + Street s1{0, std::make_pair(0, 1), 5., 50.}; + Street s2{1, std::make_pair(1, 2), 7., 70.}; + Street s3{2, std::make_pair(0, 3), 9., 90.}; + Street s4{3, std::make_pair(3, 2), 10., 100.}; + Graph graph; + graph.addStreets(s1, s2, s3, s4); + graph.buildAdj(); + Dynamics dynamics{graph, false, 69}; + dynamics.setWeightFunction(dsm::weight_functions::streetTime); + dynamics.addItinerary(0, 2); WHEN("We update the paths") { dynamics.updatePaths(); THEN("The path is updated and correctly formed") {