Documentation
¶
Overview ¶
Example 28_negative_weights — single-source shortest paths over a graph with NEGATIVE edge weights, using Bellman-Ford where Dijkstra cannot, and cross-checking the result against Johnson's all-pairs reweighting.
The subject is negative-weight routing. Dijkstra assumes non-negative edges and this module makes that explicit: search.Dijkstra returns search.ErrNegativeWeight (without traversing) the moment it sees a negative edge. search.BellmanFord is the algorithm that handles signed weights and, crucially, detects a negative cycle reachable from the source (search.ErrNegativeCycle). This example demonstrates both, and certifies the Bellman-Ford answer against two independent oracles.
Domain: a backhaul freight network with rebate lanes ¶
Freight leaves a single origin depot (the source) and flows downstream through tiers of transshipment hubs to a final tier of destination markets. Most lanes cost money (a positive shipping cost). Some lanes are BACKHAUL lanes: a carrier that would otherwise run an empty return leg pays a REBATE to fill it, so moving cargo along that lane has a negative net cost. Routing to a market therefore wants to chain rebate lanes where it can, which is exactly a negative-weight shortest-path problem.
Why there is provably no negative cycle (the acyclic instance) ¶
The freight network is a LAYERED DAG: every lane goes from tier t to tier t+1, never backward. A directed acyclic graph has no directed cycle at all, so it has no negative cycle regardless of how negative the rebate lanes are. Acyclicity is a genuine domain invariant here (goods flow one way, depot -> market), not a numerical trick, which is what lets the example carry arbitrarily large rebates and still guarantee a well-defined shortest-path answer for every seed.
The arbitrage instance (-arbitrage) ¶
A negative cycle in this domain is an "arbitrage loop": a sequence of lanes you could traverse forever, being paid net on every lap — free money, which is physically impossible and signals a modelling error. The -arbitrage flag injects exactly one back-edge (market-tier -> depot's first hub) whose weight makes the two-node loop strictly negative for ANY base weight. Bellman-Ford must then refuse to return distances and instead report search.ErrNegativeCycle. The example asserts that it does.
Correctness: a three-way oracle ¶
Bellman-Ford's single-source distances are cross-checked against two independent computations of the same quantity:
- search.JohnsonAPSP — all-pairs shortest paths via a Bellman-Ford reweighting that turns the signed graph into a non-negative one and runs Dijkstra from every vertex. For integer weights Johnson reproduces the exact distances, so the source row johnson.At(src, j) must equal Bellman-Ford's Distance(j) for every node j.
- A textbook full-edge-sweep Bellman-Ford implemented here over the public CSR neighbour API, used both to COUNT relaxation passes (the search package exposes no such counter) and as a second oracle. Its distances must equal the library's; the example fails loudly if they drift, so the instrumented counter cannot silently diverge from the engine it illustrates.
On the acyclic instance all three must agree on every distance; on the arbitrage instance all three must agree that a negative cycle exists. A disagreement is treated as a module defect and surfaced as an error.
Scale ¶
With no flags the example builds a small deterministic default whose result facts the regression test pins. Every dimension is a flag, so the same binary scales up to where the timing and allocation evidence is interesting:
go run ./examples/28_negative_weights # small deterministic default go run ./examples/28_negative_weights -layers 12 -width 4000 # observable-scale run go run ./examples/28_negative_weights -arbitrage # inject a negative cycle
Deterministic facts (bare lines) are reproducible for a fixed -seed; only telemetry (lines prefixed with "# ") varies per run and per machine.