Use edge dijkstra - #499
Conversation
4c681d9 to
75d6b64
Compare
There was a problem hiding this comment.
Pull request overview
This PR appears to migrate mobility routing and agent decision-making from node-based shortest paths to edge-based shortest paths (“edge dijkstra”), updating itinerary path generation, next-street selection, and OD-import/spawn logic accordingly.
Changes:
- Switch itinerary path computation from
allPathsTo(...)toallEdgePathsTo(...). - Update several agent spawning and transition-selection call sites to treat itinerary paths and destinations as edge-based.
- Refactor a number of
std::optionalusages to dereference via*optand add additional runtime checks/throws in evolve logic.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
| src/dsf/mobility/Street.cpp | Minor std::optional dereference change when applying speedFactor. |
| src/dsf/mobility/FirstOrderDynamics.cpp | Main routing migration: edge-path computation, spawning/origin handling, next-street selection, OD import updates, and additional runtime checks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| auto const oldSize{pItinerary->path().size()}; | ||
|
|
||
| auto const path{this->graph().allPathsTo(pItinerary->destination())}; | ||
| auto const path{this->graph().allEdgePathsTo(pItinerary->destination())}; | ||
| pItinerary->setPath(path); | ||
| auto const newSize{pItinerary->path().size()}; |
| if (m_originNodes.size() == 1) { | ||
| auto [originId, weight] = m_originNodes.at(0); | ||
| this->addAgents(nAgents, nullptr, originId); | ||
| auto const [originId, weight] = m_originNodes.at(0); | ||
| auto const originNodeId{this->graph().edge(originId).target()}; | ||
| this->addAgents(nAgents, nullptr, originNodeId); | ||
| return; |
| } else { | ||
| auto randValue{uniformDist(this->m_generator)}; | ||
| for (auto const& [origin, weight] : m_originNodes) { | ||
| for (auto const [origin, weight] : m_originNodes) { | ||
| if (randValue < weight) { | ||
| this->addAgent(nullptr, origin); | ||
| auto const originNodeId{this->graph().edge(origin).target()}; | ||
| this->addAgent(nullptr, originNodeId); | ||
| break; |
| auto const& itinerary = itineraryIt->second; | ||
| if (!itinerary->path().contains(originId)) { | ||
| spdlog::debug("Destination {} not reachable from origin {}. Skipping agent.", | ||
| destinationId, | ||
| originId); | ||
| continue; | ||
| } | ||
| this->addAgent(itineraryIt->second, originId); | ||
| auto const originNodeId{this->graph().edge(originId).source()}; | ||
| this->addAgent(itineraryIt->second, originNodeId); | ||
| } |
| if (currentStreetIdOpt.has_value()) { | ||
| auto const* pStreetCurrent{&this->graph().edge(*currentStreetIdOpt)}; | ||
| previousNodeId = pStreetCurrent->source(); | ||
| forbiddenTurns = pStreetCurrent->forbiddenTurns(); | ||
| } | ||
|
|
| // Check if this is a valid path target for non-random agents | ||
| bool bIsPathTarget = false; | ||
| bIsPathTarget = | ||
| std::find(pathTargets.cbegin(), pathTargets.cend(), streetOut.target()) != | ||
| std::find(pathTargets.cbegin(), pathTargets.cend(), streetOut.id()) != | ||
| pathTargets.cend(); |
| bool bArrived{false}; | ||
| if (!pAgent->isRandom()) { | ||
| if (pAgent->itinerary()->destination() == pStreet->target()) { | ||
| auto const& dstStreet{this->graph().edge(pAgent->itinerary()->destination())}; | ||
| if (dstStreet.source() == pStreet->target()) { | ||
| pAgent->updateItinerary(); | ||
| } | ||
| if (pAgent->itinerary()->destination() == pStreet->target()) { | ||
| if (dstStreet.source() == pStreet->target()) { | ||
| bArrived = true; | ||
| } |
| if (!pAgentTemp->isRandom()) { | ||
| if (destinationNode->id() == pAgentTemp->itinerary()->destination()) { | ||
| auto const& dstStreet{this->graph().edge(pAgentTemp->itinerary()->destination())}; | ||
| if (destinationNode->id() == dstStreet.source()) { | ||
| bArrived = true; | ||
| spdlog::debug("Agent {} has arrived at destination node {}", |
| this->graph().edges().end(), | ||
| [&](auto const& edgePair) { | ||
| auto const pathCollection = this->graph().allEdgePathsTo(edgePair.first); | ||
| allPaths.emplace(edgePair.first, std::move(pathCollection)); | ||
| }); |
| try { | ||
| auto const path = allPaths.at(targetEdgeId) | ||
| .explode(sourceEdgeId, targetEdgeId) | ||
| .front(); | ||
| if (bestPath.empty() || path.size() < bestPath.size()) { | ||
| bestPath = std::move(path); | ||
| } | ||
| } catch (...) { | ||
| continue; | ||
| } |
89292d1 to
8df9852
Compare
390bdff to
af457e7
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Suppressed comments (7)
test/mobility/Test_dynamics.cpp:829
- There is a stray standalone ';' statement in the test, which adds noise and can trigger lint/style failures.
CHECK_EQ(path.at(5).size(), 1);
CHECK_EQ(path.at(5)[0], 2);
;
src/dsf/mobility/FirstOrderDynamics.cpp:678
- Non-random agents that haven't entered the network yet (no current street ID) end up with an empty pathTargets vector, so m_nextStreetId() returns nullopt unless an errorProbability is set. m_evolveAgents() calls m_nextStreetId() when nextStreetId is missing, so these agents can be immediately removed even though a valid itinerary/path exists.
if (currentStreetIdOpt.has_value()) {
auto const* pStreetCurrent{&this->graph().edge(*currentStreetIdOpt)};
previousNodeId = pStreetCurrent->source();
forbiddenTurns = pStreetCurrent->forbiddenTurns();
// Get path targets for non-random agents
auto const& path = pAgent->itinerary()->path();
auto const pathIt = path.find(pStreetCurrent->id());
if (pathIt == path.cend()) {
spdlog::debug("No itinerary path entry for {} at {}. Returning no transition.",
*pAgent,
*pStreetCurrent);
return std::nullopt;
}
pathTargets = pathIt->second;
}
src/dsf/bindings.cpp:1235
- The Python docstrings still refer to "node ids" for the numpy overloads of setOrigins/setDestinations, but this PR switches origins/destinations to edge/street IDs. The mismatch is likely to confuse users and cause incorrect usage.
R"doc(Set origin nodes from a numpy array of node ids.
Args:
origins (array[int]): ids to use as origins.
Returns:
None)doc")
.def(
"setDestinations",
[](dsf::mobility::FirstOrderDynamics& self, nb::ndarray<dsf::Id> destinations) {
auto* ptr = static_cast<dsf::Id*>(destinations.data());
std::vector<dsf::Id> ids(ptr, ptr + destinations.size());
self.setDestinations(ids);
},
nb::arg("destinations"),
R"doc(Set destinations from a numpy array of node ids.
Args:
destinationNodes (array[int]): Node ids to use as destinations.
test/mobility/Test_traffic_simulator.cpp:340
- The inline comment still says the OD routes agents from node 0 to node 2, but the test now sets the OD to (0 -> 3). This makes the test intent unclear and can mislead future changes.
// Route agents from node 0 to node 2: they must cross both edges.
simulator.dynamics()->setODs(std::vector<std::tuple<Id, Id, double>>{{0, 3, 1.0}});
test/mobility/Test_dynamics.cpp:685
- Typo in the test description string: "iitinerary" -> "itinerary".
This issue also appears on line 827 of the same file.
WHEN("We add an iitinerary to edge 2 and update paths") {
src/dsf/mobility/FirstOrderDynamics.hpp:607
- setDestinations(TContainer) computes
1. / Nwithout guarding againstN == 0. Passing an empty container will cause a division-by-zero (and likely INF weights) before any early return is possible.
m_itineraries.clear();
auto const N{destinations.size()};
m_destinations.clear();
m_destinations.reserve(N);
double const UNIFORM_WEIGHT{1. / N};
std::for_each(destinations.begin(),
src/dsf/mobility/FirstOrderDynamics.cpp:1394
- setDestinations(initializer_list) computes
1. / Nwithout guarding againstN == 0. CallingsetDestinations({})will cause a division-by-zero and produce invalid weights.
void FirstOrderDynamics::setDestinations(std::initializer_list<Id> destinations) {
m_itineraries.clear();
auto const N{destinations.size()};
m_destinations.clear();
m_destinations.reserve(N);
double const UNIFORM_WEIGHT{1. / N};
std::for_each(destinations.begin(),
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #499 +/- ##
==========================================
- Coverage 83.99% 83.78% -0.21%
==========================================
Files 54 54
Lines 8478 8671 +193
Branches 1006 1029 +23
==========================================
+ Hits 7121 7265 +144
- Misses 1340 1387 +47
- Partials 17 19 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
d2fcaf8 to
58ea327
Compare
58ea327 to
d7a8dd0
Compare
No description provided.