Skip to content

Use edge dijkstra - #499

Merged
Grufoony merged 11 commits into
mainfrom
useEdgeDijkstra
Aug 6, 2026
Merged

Grufoony merged 11 commits into
mainfrom
useEdgeDijkstra

Conversation

@Grufoony

Copy link
Copy Markdown
Owner

No description provided.

@Grufoony
Grufoony force-pushed the useEdgeDijkstra branch 3 times, most recently from 4c681d9 to 75d6b64 Compare July 22, 2026 13:31
@Grufoony
Grufoony requested a review from Copilot July 22, 2026 14:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(...) to allEdgePathsTo(...).
  • Update several agent spawning and transition-selection call sites to treat itinerary paths and destinations as edge-based.
  • Refactor a number of std::optional usages to dereference via *opt and 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.

Comment on lines 37 to 41
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()};
Comment thread src/dsf/mobility/FirstOrderDynamics.cpp Outdated
Comment on lines 60 to 64
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;
Comment thread src/dsf/mobility/FirstOrderDynamics.cpp Outdated
Comment on lines 69 to 75
} 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;
Comment on lines 303 to 312
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);
}
Comment on lines +401 to 405
if (currentStreetIdOpt.has_value()) {
auto const* pStreetCurrent{&this->graph().edge(*currentStreetIdOpt)};
previousNodeId = pStreetCurrent->source();
forbiddenTurns = pStreetCurrent->forbiddenTurns();
}

Comment on lines 432 to 436
// 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();
Comment on lines 479 to 487
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;
}
Comment on lines 755 to 759
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 {}",
Comment thread src/dsf/mobility/FirstOrderDynamics.cpp Outdated
Comment on lines +1173 to +1177
this->graph().edges().end(),
[&](auto const& edgePair) {
auto const pathCollection = this->graph().allEdgePathsTo(edgePair.first);
allPaths.emplace(edgePair.first, std::move(pathCollection));
});
Comment thread src/dsf/mobility/FirstOrderDynamics.cpp Outdated
Comment on lines +1214 to +1223
try {
auto const path = allPaths.at(targetEdgeId)
.explode(sourceEdgeId, targetEdgeId)
.front();
if (bestPath.empty() || path.size() < bestPath.size()) {
bestPath = std::move(path);
}
} catch (...) {
continue;
}
@Grufoony
Grufoony force-pushed the useEdgeDijkstra branch 2 times, most recently from 89292d1 to 8df9852 Compare July 23, 2026 13:55
@Grufoony
Grufoony marked this pull request as ready for review August 5, 2026 14:37
@Grufoony
Grufoony requested a lite review from Copilot August 5, 2026 14:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. / N without guarding against N == 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. / N without guarding against N == 0. Calling setDestinations({}) 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(),

Comment thread examples/slow_charge_tl.cpp Outdated
Comment thread examples/slow_charge_rb.cpp Outdated
Comment thread examples/simulate_city.py Outdated
Comment thread examples/simulate_city_with_simulator.py Outdated
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.03534% with 113 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.78%. Comparing base (d1c4459) to head (ab93093).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/dsf/mobility/FirstOrderDynamics.cpp 69.90% 99 Missing ⚠️
test/mobility/Test_dynamics.cpp 97.18% 4 Missing and 2 partials ⚠️
src/dsf/mobility/TrafficSimulator.cpp 0.00% 5 Missing ⚠️
src/dsf/mobility/RoadNetwork.cpp 0.00% 3 Missing ⚠️
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     
Flag Coverage Δ
unittests 83.78% <80.03%> (-0.21%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Grufoony
Grufoony force-pushed the useEdgeDijkstra branch 2 times, most recently from d2fcaf8 to 58ea327 Compare August 5, 2026 15:13
@Grufoony
Grufoony merged commit 22e76b5 into main Aug 6, 2026
30 of 32 checks passed
@Grufoony
Grufoony deleted the useEdgeDijkstra branch August 6, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants