-
Notifications
You must be signed in to change notification settings - Fork 33
Description
The following was found by Claude while working on the prevail-rust project. To the best of my understanding, it is indeed a wasted precision issue.
In split_dbm.hpp, vert_set_wrap_t::operator[] returns true for vertices in the unstable set, but close_after_widen in graph_ops.hpp interprets its third parameter as is_stable. This inverts the stability marks: unstable vertices are marked V_STABLE (skipped) and stable vertices are marked V_UNSTABLE (recovered). Dijkstra recovery runs from the wrong vertices, missing transitive edge tightenings after widening.
Details
In split_dbm.hpp:
class vert_set_wrap_t {
public:
vert_set_wrap_t(const vert_set_t &_vs) : vs(_vs) {}
bool operator[](vert_id v) const { return vs.find(v) != vs.end(); }
// ^^^^^^^^^^^^^^^^^^^^^^^^
// true for UNSTABLE vertices
const vert_set_t &vs;
};
// ...
GrOps::close_after_widen(g_excl, potential, vert_set_wrap_t(unstable), delta);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
// passed as `is_stable`In graph_ops.hpp, close_after_widen:
for (vert_id v : g.verts()) {
edge_marks[v] = is_stable[v] ? V_STABLE : V_UNSTABLE;
// ^^^^^^^^^^^^
// For unstable vertices: true → V_STABLE (wrong)
// For stable vertices: false → V_UNSTABLE (wrong)
}
for (vert_id v : g.verts()) {
if (!edge_marks[v]) { // V_UNSTABLE == 0
dijkstra_recover(g, p, edge_marks, v, aux);
// Recovery runs from stable vertices (useless), skips unstable (needed)
}
}Impact
After widening drops some edges, the affected vertices should have their shortest paths recomputed. Instead, the recovery operates on stable vertices (which already have correct edges) and skips the unstable vertices that actually need it. This produces non-tight results — the graph after normalize() is sound but imprecise, missing transitive edges that should be recoverable.
The bug affects all configurations since DefaultParams::widen_restabilize = 1.
Reproducer
Widening two closed graphs where the right graph has a looser edge causes that edge to be dropped, making the source vertex unstable. The transitive closure through the remaining edges should recover an equivalent tight edge, but doesn't:
- Left graph edges (non-zero subgraph): 1→2: 5, 2→3: 5, 1→3: 10 (closed)
- Right graph: same but 1→3: 12 (looser)
- After widening: 1→2: 5, 2→3: 5 (edge 1→3 dropped; vertex 1 unstable)
- Expected after
close_after_widen: edge 1→3: 10 recovered via path 1→2→3 - Actual: edge 1→3 missing (recovery ran from vertices 2, 3 instead of vertex 1)
Fix
Negate the predicate in vert_set_wrap_t:
bool operator[](vert_id v) const { return vs.find(v) == vs.end(); }