Skip to content

Commit de34709

Browse files
authored
Add RoadNetwork::shortestPath function (#365)
* Add node-to-node shortestPath function * Add PathCollection class * Bump vesrion adn copilot fixes
1 parent 30a595d commit de34709

File tree

10 files changed

+720
-56
lines changed

10 files changed

+720
-56
lines changed

benchmark/Bench_Network.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ static void BM_RoadNetwork_EdgesLooping(benchmark::State& state) {
5757
}
5858
}
5959
}
60-
static void BM_RoadNetwork_ShortestPath(benchmark::State& state) {
60+
static void BM_RoadNetwork_AllPathsTo(benchmark::State& state) {
6161
dsf::mobility::RoadNetwork network;
6262
network.importEdges((DATA_FOLDER / "forlì_edges.csv").string());
6363
network.importNodeProperties((DATA_FOLDER / "forlì_nodes.csv").string());
@@ -68,12 +68,31 @@ static void BM_RoadNetwork_ShortestPath(benchmark::State& state) {
6868
++itNode;
6969
}
7070
}
71+
static void BM_RoadNetwork_ShortestPath(benchmark::State& state) {
72+
dsf::mobility::RoadNetwork network;
73+
network.importEdges((DATA_FOLDER / "forlì_edges.csv").string());
74+
network.importNodeProperties((DATA_FOLDER / "forlì_nodes.csv").string());
75+
auto itSource = network.nodes().cbegin();
76+
auto itTarget = std::next(network.nodes().cbegin(), network.nodes().size() / 2);
77+
for (auto _ : state) {
78+
auto path = network.shortestPath(itSource->first,
79+
itTarget->first,
80+
[](auto const& pEdge) { return pEdge->length(); });
81+
benchmark::DoNotOptimize(path);
82+
++itSource;
83+
++itTarget;
84+
if (itTarget == network.nodes().cend()) {
85+
itTarget = network.nodes().cbegin();
86+
}
87+
}
88+
}
7189
BENCHMARK(BM_RoadNetwork_AddNode);
7290
BENCHMARK(BM_RoadNetwork_AddEdge);
7391
BENCHMARK(BM_RoadNetwork_CSVImport);
7492
BENCHMARK(BM_RoadNetwork_GeoJSONImport);
7593
BENCHMARK(BM_RoadNetwork_NodesLooping);
7694
BENCHMARK(BM_RoadNetwork_EdgesLooping);
7795
BENCHMARK(BM_RoadNetwork_ShortestPath);
96+
BENCHMARK(BM_RoadNetwork_AllPathsTo);
7897

7998
BENCHMARK_MAIN();

src/dsf/bindings.cpp

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,104 @@ PYBIND11_MODULE(dsf_cpp, m) {
231231
&dsf::mobility::RoadNetwork::addCoil,
232232
pybind11::arg("streetId"),
233233
pybind11::arg("name") = std::string(),
234-
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::addCoil").c_str());
234+
dsf::g_docstrings.at("dsf::mobility::RoadNetwork::addCoil").c_str())
235+
.def(
236+
"shortestPath",
237+
[](const dsf::mobility::RoadNetwork& self,
238+
dsf::Id sourceId,
239+
dsf::Id targetId,
240+
dsf::PathWeight weightFunction,
241+
double threshold) {
242+
return self.shortestPath(
243+
sourceId,
244+
targetId,
245+
[weightFunction](const std::unique_ptr<dsf::mobility::Street>& street) {
246+
switch (weightFunction) {
247+
case dsf::PathWeight::LENGTH:
248+
return street->length();
249+
case dsf::PathWeight::TRAVELTIME:
250+
return street->length() / street->maxSpeed();
251+
case dsf::PathWeight::WEIGHT:
252+
return street->weight();
253+
default:
254+
return street->length() / street->maxSpeed();
255+
}
256+
},
257+
threshold);
258+
},
259+
pybind11::arg("sourceId"),
260+
pybind11::arg("targetId"),
261+
pybind11::arg("weightFunction") = dsf::PathWeight::TRAVELTIME,
262+
pybind11::arg("threshold") = 1e-9,
263+
"Find the shortest path between two nodes using Dijkstra's algorithm.\n\n"
264+
"Args:\n"
265+
" sourceId (int): The id of the source node\n"
266+
" targetId (int): The id of the target node\n"
267+
" weightFunction (PathWeight): The weight function to use (LENGTH, "
268+
"TRAVELTIME, or WEIGHT)\n"
269+
" threshold (float): A threshold value to consider alternative paths\n\n"
270+
"Returns:\n"
271+
" PathCollection: A map where each key is a node id and the value is a "
272+
"vector of next hop node ids toward the target");
273+
274+
pybind11::class_<dsf::mobility::PathCollection>(mobility, "PathCollection")
275+
.def(pybind11::init<>(), "Create an empty PathCollection")
276+
.def(
277+
"__getitem__",
278+
[](const dsf::mobility::PathCollection& self, dsf::Id key) {
279+
auto it = self.find(key);
280+
if (it == self.end()) {
281+
throw pybind11::key_error("Key not found");
282+
}
283+
return it->second;
284+
},
285+
pybind11::arg("key"),
286+
"Get the next hops for a given node id")
287+
.def(
288+
"__setitem__",
289+
[](dsf::mobility::PathCollection& self,
290+
dsf::Id key,
291+
std::vector<dsf::Id> value) { self[key] = value; },
292+
pybind11::arg("key"),
293+
pybind11::arg("value"),
294+
"Set the next hops for a given node id")
295+
.def(
296+
"__contains__",
297+
[](const dsf::mobility::PathCollection& self, dsf::Id key) {
298+
return self.find(key) != self.end();
299+
},
300+
pybind11::arg("key"),
301+
"Check if a node id exists in the collection")
302+
.def(
303+
"__len__",
304+
[](const dsf::mobility::PathCollection& self) { return self.size(); },
305+
"Get the number of nodes in the collection")
306+
.def(
307+
"keys",
308+
[](const dsf::mobility::PathCollection& self) {
309+
std::vector<dsf::Id> keys;
310+
keys.reserve(self.size());
311+
for (const auto& [key, _] : self) {
312+
keys.push_back(key);
313+
}
314+
return keys;
315+
},
316+
"Get all node ids in the collection")
317+
.def(
318+
"items",
319+
[](const dsf::mobility::PathCollection& self) {
320+
pybind11::dict items;
321+
for (const auto& [key, value] : self) {
322+
items[pybind11::int_(key)] = pybind11::cast(value);
323+
}
324+
return items;
325+
},
326+
"Get all items (node id, next hops) in the collection")
327+
.def("explode",
328+
&dsf::mobility::PathCollection::explode,
329+
pybind11::arg("sourceId"),
330+
pybind11::arg("targetId"),
331+
dsf::g_docstrings.at("dsf::mobility::PathCollection::explode").c_str());
235332

236333
pybind11::class_<dsf::mobility::Itinerary>(mobility, "Itinerary")
237334
.def(pybind11::init<dsf::Id, dsf::Id>(),

src/dsf/dsf.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
static constexpr uint8_t DSF_VERSION_MAJOR = 4;
88
static constexpr uint8_t DSF_VERSION_MINOR = 4;
9-
static constexpr uint8_t DSF_VERSION_PATCH = 0;
9+
static constexpr uint8_t DSF_VERSION_PATCH = 1;
1010

1111
static auto const DSF_VERSION =
1212
std::format("{}.{}.{}", DSF_VERSION_MAJOR, DSF_VERSION_MINOR, DSF_VERSION_PATCH);

src/dsf/mobility/Itinerary.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@ namespace dsf::mobility {
3232
inFile.close();
3333
}
3434

35-
void Itinerary::setPath(std::unordered_map<Id, std::vector<Id>> path) {
36-
m_path = std::move(path);
35+
void Itinerary::setPath(PathCollection pathCollection) {
36+
m_path = std::move(pathCollection);
3737
}
3838

3939
Id Itinerary::id() const { return m_id; }
4040
Id Itinerary::destination() const { return m_destination; }
41-
std::unordered_map<Id, std::vector<Id>> const& Itinerary::path() const {
42-
return m_path;
43-
}
41+
PathCollection const& Itinerary::path() const { return m_path; }
4442
void Itinerary::save(const std::string& fileName) const {
4543
// Open binary file
4644
std::ofstream outFile{fileName, std::ios::binary};

src/dsf/mobility/Itinerary.hpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#pragma once
1010

11+
#include "PathCollection.hpp"
1112
#include "../utility/Typedef.hpp"
1213

1314
#include <concepts>
@@ -25,7 +26,7 @@ namespace dsf::mobility {
2526
private:
2627
Id m_id;
2728
Id m_destination;
28-
std::unordered_map<Id, std::vector<Id>> m_path;
29+
PathCollection m_path;
2930

3031
public:
3132
/// @brief Construct a new Itinerary object
@@ -42,8 +43,8 @@ namespace dsf::mobility {
4243
void load(const std::string& fileName);
4344

4445
/// @brief Set the itinerary's path
45-
/// @param path An adjacency matrix made by a SparseMatrix representing the itinerary's path
46-
void setPath(std::unordered_map<Id, std::vector<Id>> path);
46+
/// @param pathCollection A dsf::mobility::PathCollection representing all equivalent paths to the destination
47+
void setPath(PathCollection pathCollection);
4748

4849
/// @brief Get the itinerary's id
4950
/// @return Id, The itinerary's id
@@ -52,9 +53,10 @@ namespace dsf::mobility {
5253
/// @return Id, The itinerary's destination
5354
Id destination() const;
5455
/// @brief Get the itinerary's path
55-
/// @return std::unordered_map<Id, std::vector<Id>> const&, The itinerary's path
56-
std::unordered_map<Id, std::vector<Id>> const& path() const;
57-
56+
/// @return PathCollection const&, The itinerary's path
57+
PathCollection const& path() const;
58+
/// @brief Save the itinerary to a binary file
59+
/// @param fileName The name of the file to save the itinerary to
5860
void save(const std::string& fileName) const;
5961
};
6062
}; // namespace dsf::mobility
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include "PathCollection.hpp"
2+
3+
std::list<std::vector<dsf::Id>> dsf::mobility::PathCollection::explode(
4+
Id const sourceId, Id const targetId) const {
5+
std::list<std::vector<Id>> paths;
6+
7+
// Base case: if source equals target, return a path with just the source
8+
if (sourceId == targetId) {
9+
paths.push_back({sourceId});
10+
return paths;
11+
}
12+
13+
// Check if sourceId exists in the map
14+
auto it = this->find(sourceId);
15+
if (it == this->end()) {
16+
return paths; // No paths available from this source
17+
}
18+
19+
auto const& nextHops = it->second;
20+
21+
// For each possible next hop from sourceId
22+
for (auto const& hop : nextHops) {
23+
if (hop == targetId) {
24+
// Direct path found
25+
paths.push_back({sourceId, targetId});
26+
} else {
27+
// Recursively find paths from hop to target
28+
auto subPaths = explode(hop, targetId);
29+
30+
// Prepend sourceId to each sub-path
31+
for (auto& subPath : subPaths) {
32+
subPath.insert(subPath.begin(), sourceId);
33+
paths.push_back(std::move(subPath));
34+
}
35+
}
36+
}
37+
38+
return paths;
39+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include "../utility/Typedef.hpp"
4+
5+
#include <list>
6+
#include <unordered_map>
7+
#include <vector>
8+
9+
namespace dsf::mobility {
10+
class PathCollection : public std::unordered_map<Id, std::vector<Id>> {
11+
public:
12+
using std::unordered_map<Id, std::vector<Id>>::unordered_map; // Inherit constructors
13+
14+
/// @brief Explode all possible paths from sourceId to targetId
15+
/// @param sourceId The starting point of the paths
16+
/// @param targetId The end point of the paths
17+
/// @return A list of vectors, each vector representing a path from sourceId to targetId
18+
std::list<std::vector<Id>> explode(Id const sourceId, Id const targetId) const;
19+
};
20+
} // namespace dsf::mobility

0 commit comments

Comments
 (0)