bug : Bellman-Ford/DAG ignore negative costs despite documented support — base_graph filtering

#3148 · open · 0 comments

View on GitHub ↗

AdeshDeshmukh

<!--Hi! We prefilled this form for you with things that should make this it easier for you to let us know what is going on. You can freely remove what is not necessary or add what is missing. Thanks! --> **Problem** <!--Short is sweet: What doesn't work for you? When/Where do you have that problem?--> The documented negative-cost support for `pgr_bellmanFord`, `pgr_edwardMoore`, and `pgr_dagShortestPath` does not actually work. `doc/bellman_ford/pgr_bellmanFord.rst:42,47` states some edge weights may be negative and the process is valid for edges with both positive and negative weights. The algorithm wrappers (`include/bellman_ford/bellman_ford.hpp:132` `boost::bellman_ford_shortest_paths`, `include/bellman_ford/edwardMoore.hpp:180` queue relaxation, and `include/dagShortestPath/dagShortestPath.hpp:131` `boost::dag_shortest_paths`) all handle negatives. However, every call goes through `src/dijkstra/shortestPath_driver.cpp:240,268` (`digraph.insert_edges(edges)`) → `include/cpp_common/base_graph.hpp:732-761` (`graph_add_edge`), where edges are guarded with `if(cost>=0)` and `if(reverse>=0)`. Negatives are dropped before the algorithms ever receive them. This matches the Dijkstra contract (`doc/src/pgRouting-concepts.rst:85,108` and `doc/dijkstra/dijkstra-family.rst:57-59`), but directly contradicts the Bellman and DAG contracts. The sibling helper `base_graph.hpp:815-850` (`graph_add_neg_edge`) converts negative costs via `(-0.5) * cost` with a `TODO` at line 816, so it cannot be reused as-is. **To Reproduce** <!--How can we see this by ourselves?--> ```sql -- Bellman: negative middle edge should be used SELECT * FROM pgr_bellmanFord( 'SELECT * FROM (VALUES (1,1,2,5),(2,2,3,-4),(3,1,3,10)) AS t(id,source,target,cost)', 1, 3, true); -- Expected Bellman: path 1->2->3, agg_cost = 1 -- Actual today: path 1->3, agg_cost = 10 (the -4 edge was filtered out) -- Bellman: negative reverse_cost on undirected graph SELECT * FROM pgr_bellmanFord( 'SELECT * FROM (VALUES (1,1,2,5,2),(2,2,3,10,-3)) AS t(id,source,target,cost,reverse_cost)', 1, 3, false); -- Expected: reverse edge with negative cost is traversed; Actual today: ignored -- DAG: negative on an acyclic graph should be used SELECT * FROM pgr_dagShortestPath( 'SELECT * FROM (VALUES (1,1,2,-2),(2,2,3,1),(3,1,3,5)) AS t(id,source,target,cost)', 1, 3); -- Expected: path 1->2->3, agg_cost = -1 -- Actual today: path 1->3, agg_cost = 5 -- Dijkstra on identical data should preserve current behavior (negative = edge does not exist) SELECT * FROM pgr_dijkstra( 'SELECT * FROM (VALUES (1,1,2,5),(2,2,3,-4),(3,1,3,10)) AS t(id,source,target,cost)', 1, 3, true); -- Expected & Actual: path 1->3, agg_cost = 10 (filtering preserved) ``` **Expectation** <!--Some problems are easier to explain by letting us know what you expected to happen.--> - `pgr_bellmanFord`/`pgr_edwardMoore` (directed and undirected) and `pgr_dagShortestPath` (directed) include both negative `cost` and `reverse_cost` values in the graph so underlying solvers (`boost::bellman_ford_shortest_paths`, `edwardMoore`, `dag_shortest_paths`) can route through them. - `pgr_dijkstra`, `pgr_bdDijkstra`, `pgr_aStar`, and `pgr_bdAstar` preserve their current filter (`cost < 0` implies the edge does not exist). - No changes to public SQL signatures or empty-SQL query checks (`ERROR: Empty edges SQL` at `shortestPath_driver.cpp:145-148`). **Sample Data** <!--Having sample data can make it easier to understand where you struggle!--> The inline `VALUES` queries above reproduce the issue directly. Alternatively, using the standard sample dataset: ```sql INSERT INTO edges(id, source, target, cost) VALUES (-1, 1, 2, -4); ``` **Platform/versions** <!--We work in various environments. It would be good to know what versions you use.--> ```sql SELECT version(); -- PostgreSQL 17.x (affects 13-18, logic is in the C++ graph layer) SELECT postgis_full_version(); SELECT pgr_version(); -- 4.1.0-dev, develop @ 350b1e533 ``` - OS: Ubuntu 24.04 (platform-independent C++ graph logic) **Proposed approach** Minimal two-file fix without changing SQL signatures: 1. Add `insert_edges_allow_negative` / `graph_add_edge_allow_negative` to `include/cpp_common/base_graph.hpp` (a non-filtering variant that stores `cost` and `reverse_cost` as-is, unlike `graph_add_edge` at lines 732–761). 2. Route `BELLMANFORD`, `EDWARDMOORE`, and `DAGSP` through this new insertion path in `src/dijkstra/shortestPath_driver.cpp:239-292` by matching on `Which` (`include/c_common/enums.h:34-36`), ensuring DAG applies only to directed calls. 3. Keep Dijkstra/BDD/A*/BFS/WITHPOINTS on the existing filtered path. 4. Add pgTAP regression tests covering negative `cost` and `reverse_cost` for Bellman-Ford and DAG. Happy to follow ur direction ! :)

Comments