Skip to content

edgeDisjoint bellman ford use process driver#3090

Open
cvvergara wants to merge 8 commits intopgRouting:developfrom
cvvergara:edgeDisjoint-bellmanFord-use-process-driver
Open

edgeDisjoint bellman ford use process driver#3090
cvvergara wants to merge 8 commits intopgRouting:developfrom
cvvergara:edgeDisjoint-bellmanFord-use-process-driver

Conversation

@cvvergara
Copy link
Member

@cvvergara cvvergara commented Mar 4, 2026

Fixes #3089

Changes proposed in this pull request:

  • Use the shortestPath process and driver on pgr_edgeDisjointPaths and pgr_bellmanFord functions
  • removes unused code

@pgRouting/admins

Summary by CodeRabbit

@cvvergara cvvergara added this to the Release 4.1.0 milestone Mar 4, 2026
@cvvergara cvvergara requested a review from robe2 March 4, 2026 01:54
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Walkthrough

This PR consolidates the bellmanFord and edgeDisjoint algorithms to use a centralized shortestPath driver and process mechanism. Dedicated algorithm-specific drivers are removed, replaced with unified C++ function handlers. New enum values (BELLMANFORD, EDGEDISJOINT) are added to the Which enum, and supporting utilities updated to dispatch to the appropriate implementation. The refactoring standardizes how these algorithms integrate with the core routing framework.

Changes

Cohort / File(s) Summary
Release Notes & Documentation
NEWS.md, doc/src/release_notes.rst, locale/pot/pgrouting_doc_strings.pot, locale/en/LC_MESSAGES/pgrouting_doc_strings.po
Added release-note entry for issue #3089 documenting bellmanFord and edgeDisjoint using shortestPath driver and process. Updated POT-Creation-Date metadata.
Enum & Public API Declarations
include/c_common/enums.h
Added BELLMANFORD and EDGEDISJOINT enum values to Which enum for algorithm dispatching.
Bellman-Ford Header Refactor
include/bellman_ford/bellman_ford.hpp
Moved Pgr_bellman_ford class to pgrouting::algorithms namespace. Added new public wrapper bellmanFord() in pgrouting::functions that delegates to algorithms::Pgr_bellman_ford and post-processes paths via recalculate_agg_cost().
Edge-Disjoint Paths Header
include/max_flow/maxflow.hpp
Added new public function edgeDisjoint() in pgrouting::functions accepting Edge_t vector and combinations map, returning vector<Path_rt>.
Removed Bellman-Ford Driver Headers
include/drivers/bellman_ford/bellman_ford_driver.h, include/drivers/bellman_ford/bellman_ford_neg_driver.h
Removed public driver headers exposing pgr_do_bellman_ford and pgr_do_bellman_ford_neg functions. These are no longer direct public API entry points.
Removed Edge-Disjoint Driver Header
include/drivers/max_flow/edge_disjoint_paths_driver.h
Removed public driver header exposing pgr_do_edge_disjoint_paths function.
Utilities & Common Functions
include/cpp_common/combinations.hpp, include/cpp_common/to_postgres.hpp
Removed get_combinations overloads returning map; added new get_tuples overload for Path_rt vector with Edge_t vector support for edge-based cost calculations.
Build Configuration
src/bellman_ford/CMakeLists.txt, src/max_flow/CMakeLists.txt
Removed bellman_ford_driver.cpp, bellman_ford_neg_driver.cpp, and edge_disjoint_paths_driver.cpp from build targets. Only core algorithm implementations retained.
Bellman-Ford C Process
src/bellman_ford/bellman_ford.c
Refactored to use pgr_process_shortestPath for centralized processing. Removed standalone process() function. Introduced local call_cntr tracking. Updated result assembly logic to use process-based tuple generation.
Bellman-Ford Driver Implementation Removal
src/bellman_ford/bellman_ford_driver.cpp, src/bellman_ford/bellman_ford_neg_driver.cpp
Removed entire driver implementations. pgr_do_bellman_ford and pgr_do_bellman_ford_neg functions deleted; functionality migrated to centralized shortestPath flow.
Edge-Disjoint Paths C Process
src/max_flow/edge_disjoint_paths.c
Refactored to use pgr_process_shortestPath for centralized processing. Updated SRF result generation to use call_cntr-based scheme. Removed dedicated process() wrapper.
Edge-Disjoint Driver Implementation Removal
src/max_flow/edge_disjoint_paths_driver.cpp
Removed entire driver implementation. pgr_do_edge_disjoint_paths function deleted; single_execution helper logic migrated to maxflow.cpp.
C++ Algorithm Implementations
src/bellman_ford/bellman_ford.c, src/max_flow/maxflow.cpp
bellman_ford.c: Added bellmanFord() wrapper function delegating to algorithms::Pgr_bellman_ford. maxflow.cpp: Added edgeDisjoint() function with internal single_execution helper iterating combinations and aggregating results.
Utility Function Updates
src/cpp_common/combinations.cpp, src/cpp_common/to_postgres.cpp, src/cpp_common/utilities.cpp
Removed get_combinations(deque&, trsp::Rule vector) overload. Added get_tuples() overload for Path_rt vectors with edge-based cost recalculation. Extended get_name() and estimate_drivingSide() to handle BELLMANFORD and EDGEDISJOINT cases.
ShortestPath Driver Updates
src/dijkstra/shortestPath_driver.cpp
Added includes for bellman_ford and maxflow. Introduced control flow branches for BELLMANFORD (directed/undirected) and EDGEDISJOINT cases. New branches call bellmanFord() or edgeDisjoint() and return results via get_tuples().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Enhancement, C/C++

Suggested reviewers

  • robe2

Poem

🐰 A hop, skip, and jump through the code we go,
bellmanFord and edgeDisjoint steal the show!
Through shortestPath's driver they now find their way,
Unified and standardized, hip hip hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions key changes: edgeDisjoint and bellman ford switching to the process driver, which aligns with the PR's main objective of standardizing these functions to use shortestPath process/driver.
Linked Issues check ✅ Passed The PR successfully implements issue #3089 requirements: edgeDisjoint and bellmanFord now use shortestPath process/driver via pgr_process_shortestPath, old driver headers/implementations removed, and new wrapper functions added in C++ API.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the objective: new enum values (BELLMANFORD, EDGEDISJOINT) added, driver implementations removed, integration with shortestPath driver completed, and release notes updated appropriately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/cpp_common/utilities.cpp (1)

179-186: 🧹 Nitpick | 🔵 Trivial

Handle EDGEDISJOINT as non-driving-side in estimate_drivingSide.

EDGEDISJOINT currently falls into default, which can coerce ' ' into 'b'. For consistency with non-driving-side shortest-path modes, it should return ' ' directly.

♻️ Proposed change
     switch (which) {
         case ASTAR :
         case BDASTAR :
         case DAGSP :
         case EDWARDMOORE:
         case BELLMANFORD :
+        case EDGEDISJOINT:
         case BDDIJKSTRA:
         case DIJKSTRA:
                 return ' ';
                 break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpp_common/utilities.cpp` around lines 179 - 186, The switch in
estimate_drivingSide treats EDGEDISJOINT via the default path which can coerce '
' into 'b'; add EDGEDISJOINT as one of the explicit cases that return ' ' so it
is handled as non-driving-side like
ASTAR/BDASTAR/DAGSP/EDWARDMOORE/BELLMANFORD/BDDIJKSTRA/DIJKSTRA, i.e. insert
"case EDGEDISJOINT:" into the same case group that returns ' ' to ensure
consistent behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/c_common/enums.h`:
- Around line 36-37: The get_name(Which) overload in
src/cpp_common/utilities.cpp currently returns "unknown" for BELLMANFORD and
EDGEDISJOINT; locate the get_name(Which) function and either (a) confirm no
reachable call sites pass the new enum values by searching call sites that pass
Which, or (b) add explicit switch cases for BELLMANFORD and EDGEDISJOINT in
get_name(Which) so they map to the proper string names (use the enum identifiers
BELLMANFORD and EDGEDISJOINT to find the exact enum and implement the
corresponding case arms), ensuring the function never falls through to "unknown"
for these values.

In `@src/cpp_common/to_postgres.cpp`:
- Around line 317-326: The loop that assigns costs to path entries (variables
paths, edges, found, r.cost, e.id, e.source, e.cost, e.reverse_cost) can leave
routable rows (r.edge >= 0) unresolved; add a final sanity check after the loop
that scans paths for any entry with r.edge >= 0 whose r.cost is still
unset/invalid and if any are found abort/throw/log a fatal error (e.g., mirror
pgRouting's "No result generated, report this error") so unresolved mappings
cannot silently return stale costs; ensure the check only considers routable
edges (r.edge >= 0) and includes a clear message identifying the inconsistency
before returning tuples.

In `@src/max_flow/maxflow.cpp`:
- Around line 39-43: The functions single_execution and edgeDisjoint currently
take std::vector<Edge_t> by value which causes expensive copies when
edgeDisjoint repeatedly calls single_execution; change both function signatures
to take const std::vector<Edge_t>& edges (and adjust any forward
declarations/overloads) so the vector is passed by const reference. Ensure
inside single_execution you do not mutate edges; if mutation is required, make a
local copy there only. Update all call sites (edgeDisjoint -> single_execution)
to match the new signature.

---

Outside diff comments:
In `@src/cpp_common/utilities.cpp`:
- Around line 179-186: The switch in estimate_drivingSide treats EDGEDISJOINT
via the default path which can coerce ' ' into 'b'; add EDGEDISJOINT as one of
the explicit cases that return ' ' so it is handled as non-driving-side like
ASTAR/BDASTAR/DAGSP/EDWARDMOORE/BELLMANFORD/BDDIJKSTRA/DIJKSTRA, i.e. insert
"case EDGEDISJOINT:" into the same case group that returns ' ' to ensure
consistent behavior.

ℹ️ Review info

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0198519e-4102-48b6-a877-009b3cb174f0

📥 Commits

Reviewing files that changed from the base of the PR and between be2bd04 and 5e217dd.

📒 Files selected for processing (24)
  • NEWS.md
  • doc/src/release_notes.rst
  • include/bellman_ford/bellman_ford.hpp
  • include/c_common/enums.h
  • include/cpp_common/combinations.hpp
  • include/cpp_common/to_postgres.hpp
  • include/drivers/bellman_ford/bellman_ford_driver.h
  • include/drivers/bellman_ford/bellman_ford_neg_driver.h
  • include/drivers/max_flow/edge_disjoint_paths_driver.h
  • include/max_flow/maxflow.hpp
  • locale/en/LC_MESSAGES/pgrouting_doc_strings.po
  • locale/pot/pgrouting_doc_strings.pot
  • src/bellman_ford/CMakeLists.txt
  • src/bellman_ford/bellman_ford.c
  • src/bellman_ford/bellman_ford_driver.cpp
  • src/bellman_ford/bellman_ford_neg_driver.cpp
  • src/cpp_common/combinations.cpp
  • src/cpp_common/to_postgres.cpp
  • src/cpp_common/utilities.cpp
  • src/dijkstra/shortestPath_driver.cpp
  • src/max_flow/CMakeLists.txt
  • src/max_flow/edge_disjoint_paths.c
  • src/max_flow/edge_disjoint_paths_driver.cpp
  • src/max_flow/maxflow.cpp
💤 Files with no reviewable changes (7)
  • src/max_flow/edge_disjoint_paths_driver.cpp
  • include/drivers/max_flow/edge_disjoint_paths_driver.h
  • include/drivers/bellman_ford/bellman_ford_driver.h
  • src/bellman_ford/bellman_ford_driver.cpp
  • src/bellman_ford/bellman_ford_neg_driver.cpp
  • include/drivers/bellman_ford/bellman_ford_neg_driver.h
  • src/bellman_ford/CMakeLists.txt

Comment on lines +36 to +37
BELLMANFORD,
EDGEDISJOINT,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify get_name(Which) coverage for newly added enum values.

BELLMANFORD and EDGEDISJOINT are added here, but src/cpp_common/utilities.cpp (the get_name(Which) overload) appears to fall through to "unknown" for these values. Please confirm whether that overload is reachable in these paths and add cases if needed.

#!/bin/bash
set -euo pipefail

echo "=== get_name(Which) implementation ==="
rg -n -A120 -B5 'get_name\s*\(\s*Which\s+which\s*\)' src/cpp_common/utilities.cpp

echo
echo "=== Call sites using get_name(...) ==="
rg -nP '\bget_name\s*\(' src include

echo
echo "=== New enum usage sites ==="
rg -nP '\bBELLMANFORD\b|\bEDGEDISJOINT\b' src include

Expected result: either (a) no reachable path uses get_name(Which) for these enums, or (b) switch cases are added so it never returns "unknown" for them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/c_common/enums.h` around lines 36 - 37, The get_name(Which) overload
in src/cpp_common/utilities.cpp currently returns "unknown" for BELLMANFORD and
EDGEDISJOINT; locate the get_name(Which) function and either (a) confirm no
reachable call sites pass the new enum values by searching call sites that pass
Which, or (b) add explicit switch cases for BELLMANFORD and EDGEDISJOINT in
get_name(Which) so they map to the proper string names (use the enum identifiers
BELLMANFORD and EDGEDISJOINT to find the exact enum and implement the
corresponding case arms), ensuring the function never falls through to "unknown"
for these values.

Comment on lines +317 to +326
auto found = paths.size();
for (const auto &e : edges) {
for (auto &r : paths) {
if (r.edge == e.id) {
r.cost = (r.node == e.source) ? e.cost : e.reverse_cost;
--found;
}
}
if (found == 0) break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a hard sanity check for unresolved path edges before returning tuples.

At Line 317, the completion counter does not guarantee that every routable (edge >= 0) row was resolved. Any unmatched edge id currently leaves r.cost silently stale, which can return incorrect results.

🛠️ Suggested fix (validate mappings and direction explicitly)
-    auto found = paths.size();
-    for (const auto &e : edges) {
-        for (auto &r : paths) {
-            if (r.edge == e.id) {
-                r.cost = (r.node == e.source) ?  e.cost : e.reverse_cost;
-                --found;
-            }
-        }
-        if (found == 0) break;
-    }
+    std::map<int64_t, const Edge_t*> edge_by_id;
+    for (const auto &e : edges) {
+        edge_by_id[e.id] = &e;
+    }
+
+    size_t unresolved = 0;
+    for (auto &r : paths) {
+        if (r.edge < 0) continue;
+        auto it = edge_by_id.find(r.edge);
+        if (it == edge_by_id.end()) {
+            ++unresolved;
+            continue;
+        }
+
+        const auto &e = *(it->second);
+        if (r.node == e.source) {
+            r.cost = e.cost;
+        } else if (r.node == e.target) {
+            r.cost = e.reverse_cost;
+        } else {
+            ++unresolved;
+        }
+    }
+    pgassert(unresolved == 0);

Based on learnings: In pgRouting's allpairs driver, the "No result generated, report this error" return_count check is used as an internal sanity check to catch implementation bugs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cpp_common/to_postgres.cpp` around lines 317 - 326, The loop that assigns
costs to path entries (variables paths, edges, found, r.cost, e.id, e.source,
e.cost, e.reverse_cost) can leave routable rows (r.edge >= 0) unresolved; add a
final sanity check after the loop that scans paths for any entry with r.edge >=
0 whose r.cost is still unset/invalid and if any are found abort/throw/log a
fatal error (e.g., mirror pgRouting's "No result generated, report this error")
so unresolved mappings cannot silently return stale costs; ensure the check only
considers routable edges (r.edge >= 0) and includes a clear message identifying
the inconsistency before returning tuples.

Comment on lines +39 to +43
single_execution(
std::vector<Edge_t> edges,
int64_t source,
int64_t target,
bool directed) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check if the file exists and get basic info
if [ ! -f "src/max_flow/maxflow.cpp" ]; then
    echo "File not found: src/max_flow/maxflow.cpp"
    exit 1
fi

echo "=== Checking file structure ==="
wc -l src/max_flow/maxflow.cpp

echo -e "\n=== Lines 39-50 (single_execution function) ==="
sed -n '39,50p' src/max_flow/maxflow.cpp

echo -e "\n=== Lines 342-355 (edgeDisjoint function) ==="
sed -n '342,355p' src/max_flow/maxflow.cpp

echo -e "\n=== Searching for single_execution calls ==="
rg -n "single_execution\s*\(" src/max_flow/maxflow.cpp | head -20

Repository: pgRouting/pgrouting

Length of output: 1293


🏁 Script executed:

#!/bin/bash
# Check header file for declarations
echo "=== Checking header file ==="
if [ -f "include/max_flow/maxflow.hpp" ]; then
    echo "Header file exists. Searching for function declarations:"
    echo -e "\n--- edgeDisjoint declaration ---"
    rg -A 2 "edgeDisjoint" include/max_flow/maxflow.hpp
    echo -e "\n--- single_execution declaration ---"
    rg -A 2 "single_execution" include/max_flow/maxflow.hpp
else
    echo "Header file not found"
fi

Repository: pgRouting/pgrouting

Length of output: 354


🏁 Script executed:

#!/bin/bash
# Check context around the call to single_execution
echo "=== Context around single_execution call in edgeDisjoint ==="
sed -n '342,370p' src/max_flow/maxflow.cpp

Repository: pgRouting/pgrouting

Length of output: 767


Change edges parameter to const reference in both functions to avoid repeated vector copies in the nested loop.

single_execution and edgeDisjoint both take std::vector<Edge_t> by value. Since edgeDisjoint calls single_execution(edges, ...) inside nested loops over source-target combinations, this creates redundant full-vector copies that degrade performance for large edge sets.

Proposed fix
--- a/src/max_flow/maxflow.cpp
+++ b/src/max_flow/maxflow.cpp
@@ -39,7 +39,7 @@ namespace pgrouting::functions {
 
 std::vector<Path_rt>
 single_execution(
-        std::vector<Edge_t> edges,
+        const std::vector<Edge_t> &edges,
         int64_t source,
         int64_t target,
         bool directed) {
@@ -342,7 +342,7 @@ single_execution(
 
 std::vector<Path_rt>
 edgeDisjoint(
-        std::vector<Edge_t> edges,
+        const std::vector<Edge_t> &edges,
         const std::map<int64_t, std::set<int64_t>> & combinations,
         bool directed) {
--- a/include/max_flow/maxflow.hpp
+++ b/include/max_flow/maxflow.hpp
-std::vector<Path_rt> edgeDisjoint(std::vector<Edge_t>, const std::map<int64_t, std::set<int64_t>>&, bool);
+std::vector<Path_rt> edgeDisjoint(const std::vector<Edge_t>&, const std::map<int64_t, std::set<int64_t>>&, bool);
🧰 Tools
🪛 Cppcheck (2.19.0)

[information] 41-41: Include file

(missingIncludeSystem)


[information] 42-42: Include file

(missingIncludeSystem)


[information] 43-43: Include file

(missingIncludeSystem)


[performance] 40-40: Function parameter 'edges' should be passed by const reference.

(passedByValue)


[information] 41-41: Include file

(missingIncludeSystem)


[information] 42-42: Include file

(missingIncludeSystem)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/max_flow/maxflow.cpp` around lines 39 - 43, The functions
single_execution and edgeDisjoint currently take std::vector<Edge_t> by value
which causes expensive copies when edgeDisjoint repeatedly calls
single_execution; change both function signatures to take const
std::vector<Edge_t>& edges (and adjust any forward declarations/overloads) so
the vector is passed by const reference. Ensure inside single_execution you do
not mutate edges; if mutation is required, make a local copy there only. Update
all call sites (edgeDisjoint -> single_execution) to match the new signature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

edgeDisjoint and bellmanFord use shortestPath driver and process

1 participant