Skip to content

Commit 1a84935

Browse files
committed
Add PathCollection class
1 parent 790da1b commit 1a84935

File tree

8 files changed

+340
-38
lines changed

8 files changed

+340
-38
lines changed

src/dsf/bindings.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,103 @@ 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, TRAVELTIME, or WEIGHT)\n"
268+
" threshold (float): A threshold value to consider alternative paths\n\n"
269+
"Returns:\n"
270+
" PathCollection: A map where each key is a node id and the value is a vector of next hop node ids toward the target");
271+
272+
pybind11::class_<dsf::mobility::PathCollection>(mobility, "PathCollection")
273+
.def(pybind11::init<>(), "Create an empty PathCollection")
274+
.def(
275+
"__getitem__",
276+
[](const dsf::mobility::PathCollection& self, dsf::Id key) {
277+
auto it = self.find(key);
278+
if (it == self.end()) {
279+
throw pybind11::key_error("Key not found");
280+
}
281+
return it->second;
282+
},
283+
pybind11::arg("key"),
284+
"Get the next hops for a given node id")
285+
.def(
286+
"__setitem__",
287+
[](dsf::mobility::PathCollection& self, dsf::Id key, std::vector<dsf::Id> value) {
288+
self[key] = value;
289+
},
290+
pybind11::arg("key"),
291+
pybind11::arg("value"),
292+
"Set the next hops for a given node id")
293+
.def(
294+
"__contains__",
295+
[](const dsf::mobility::PathCollection& self, dsf::Id key) {
296+
return self.find(key) != self.end();
297+
},
298+
pybind11::arg("key"),
299+
"Check if a node id exists in the collection")
300+
.def(
301+
"__len__",
302+
[](const dsf::mobility::PathCollection& self) { return self.size(); },
303+
"Get the number of nodes in the collection")
304+
.def(
305+
"keys",
306+
[](const dsf::mobility::PathCollection& self) {
307+
std::vector<dsf::Id> keys;
308+
keys.reserve(self.size());
309+
for (const auto& [key, _] : self) {
310+
keys.push_back(key);
311+
}
312+
return keys;
313+
},
314+
"Get all node ids in the collection")
315+
.def(
316+
"items",
317+
[](const dsf::mobility::PathCollection& self) {
318+
pybind11::dict items;
319+
for (const auto& [key, value] : self) {
320+
items[pybind11::int_(key)] = pybind11::cast(value);
321+
}
322+
return items;
323+
},
324+
"Get all items (node id, next hops) in the collection")
325+
.def(
326+
"explode",
327+
&dsf::mobility::PathCollection::explode,
328+
pybind11::arg("sourceId"),
329+
pybind11::arg("targetId"),
330+
dsf::g_docstrings.at("dsf::mobility::PathCollection::explode").c_str());
235331

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

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 path);
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

src/dsf/mobility/RoadNetwork.hpp

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "RoadJunction.hpp"
1515
#include "Intersection.hpp"
1616
#include "TrafficLight.hpp"
17+
#include "PathCollection.hpp"
1718
#include "Roundabout.hpp"
1819
#include "Station.hpp"
1920
#include "Street.hpp"
@@ -204,10 +205,9 @@ namespace dsf::mobility {
204205
/// @throws std::invalid_argument if the dynamics function is not callable with a const reference
205206
template <typename DynamicsFunc>
206207
requires(std::is_invocable_r_v<double, DynamicsFunc, std::unique_ptr<Street> const&>)
207-
std::unordered_map<Id, std::vector<Id>> allPathsTo(
208-
Id const targetId,
209-
DynamicsFunc getEdgeWeight,
210-
double const threshold = 1e-9) const;
208+
PathCollection allPathsTo(Id const targetId,
209+
DynamicsFunc getEdgeWeight,
210+
double const threshold = 1e-9) const;
211211

212212
/// @brief Find the shortest path between two nodes using Dijkstra's algorithm
213213
/// @tparam DynamicsFunc A callable type that takes a const reference to a Street and returns a double representing the edge weight
@@ -221,11 +221,10 @@ namespace dsf::mobility {
221221
/// Like allPathsTo, this method tracks all equivalent paths within the threshold, allowing for multiple next hops per node.
222222
template <typename DynamicsFunc>
223223
requires(std::is_invocable_r_v<double, DynamicsFunc, std::unique_ptr<Street> const&>)
224-
std::unordered_map<Id, std::vector<Id>> shortestPath(
225-
Id const sourceId,
226-
Id const targetId,
227-
DynamicsFunc getEdgeWeight,
228-
double const threshold = 1e-9) const;
224+
PathCollection shortestPath(Id const sourceId,
225+
Id const targetId,
226+
DynamicsFunc getEdgeWeight,
227+
double const threshold = 1e-9) const;
229228
};
230229

231230
template <typename... TArgs>
@@ -312,16 +311,17 @@ namespace dsf::mobility {
312311

313312
template <typename DynamicsFunc>
314313
requires(std::is_invocable_r_v<double, DynamicsFunc, std::unique_ptr<Street> const&>)
315-
std::unordered_map<Id, std::vector<Id>> RoadNetwork::allPathsTo(
316-
Id const targetId, DynamicsFunc f, double const threshold) const {
314+
PathCollection RoadNetwork::allPathsTo(Id const targetId,
315+
DynamicsFunc f,
316+
double const threshold) const {
317317
// Check if source node exists
318318
auto const& nodes = this->nodes();
319319

320320
// Distance from each node to the source (going backward)
321321
std::unordered_map<Id, double> distToTarget;
322322
distToTarget.reserve(nNodes());
323323
// Next hop from each node toward the source
324-
std::unordered_map<Id, std::vector<Id>> nextHopsToTarget;
324+
PathCollection nextHopsToTarget;
325325

326326
// Priority queue: pair<distance, nodeId> (min-heap)
327327
std::priority_queue<std::pair<double, Id>,
@@ -383,7 +383,7 @@ namespace dsf::mobility {
383383
}
384384

385385
// Build result: only include reachable nodes (excluding source)
386-
std::unordered_map<Id, std::vector<Id>> result;
386+
PathCollection result;
387387
for (auto const& [nodeId, hops] : nextHopsToTarget) {
388388
if (nodeId != targetId &&
389389
distToTarget[nodeId] != std::numeric_limits<double>::infinity() &&
@@ -397,11 +397,13 @@ namespace dsf::mobility {
397397

398398
template <typename DynamicsFunc>
399399
requires(std::is_invocable_r_v<double, DynamicsFunc, std::unique_ptr<Street> const&>)
400-
std::unordered_map<Id, std::vector<Id>> RoadNetwork::shortestPath(
401-
Id const sourceId, Id const targetId, DynamicsFunc f, double const threshold) const {
400+
PathCollection RoadNetwork::shortestPath(Id const sourceId,
401+
Id const targetId,
402+
DynamicsFunc f,
403+
double const threshold) const {
402404
// If source equals target, return empty map (no intermediate hops needed)
403405
if (sourceId == targetId) {
404-
return std::unordered_map<Id, std::vector<Id>>{};
406+
return PathCollection{};
405407
}
406408
// Check if source node exists
407409
if (!this->nodes().contains(sourceId)) {
@@ -419,7 +421,7 @@ namespace dsf::mobility {
419421
std::unordered_map<Id, double> distToTarget;
420422
distToTarget.reserve(nNodes());
421423
// Next hop from each node toward the target
422-
std::unordered_map<Id, std::vector<Id>> nextHopsToTarget;
424+
PathCollection nextHopsToTarget;
423425

424426
// Priority queue: pair<distance, nodeId> (min-heap)
425427
std::priority_queue<std::pair<double, Id>,
@@ -487,11 +489,11 @@ namespace dsf::mobility {
487489

488490
// Check if target is reachable from source
489491
if (distToTarget[sourceId] == std::numeric_limits<double>::infinity()) {
490-
return std::unordered_map<Id, std::vector<Id>>{};
492+
return PathCollection{};
491493
}
492494

493495
// Build result: only include nodes on the path from source to target
494-
std::unordered_map<Id, std::vector<Id>> result;
496+
PathCollection result;
495497
std::unordered_set<Id> nodesOnPath;
496498

497499
// Start from source and traverse to target using BFS to find all nodes on valid paths

0 commit comments

Comments
 (0)