Problem: bellman_ford

Your task is to implement the Bellman-Ford Algorithm and verify its functional correctness in Dafny. Unlike Dijkstra, this algorithm must handle graphs with negative edge weights. Preconditions: You can assume the graph contains at most 100,000 nodes and edge weights are between -100,000 and 100,000 (inclusive) to prevent integer overflow. Crucially, the graph is a Simple Graph (no parallel edges between the same two nodes). Requirements: Implement the function bellman_ford which returns Option<seq<Option<int>>>. Case 1 (Success): If the graph contains no negative cycles reachable from the start node, return Some(dists). dists[v] = Some(d) implies d is the shortest path weight. dists[v] = None implies v is unreachable. Case 2 (Negative Cycle): If the graph contains a reachable negative cycle (a cycle with total weight $< 0$), return None. Verification Challenges: (1) Relaxation Loop: The algorithm runs for $|V|-1$ iterations. You must prove the invariant that after $k$ iterations, dist[v] contains the shortest path weight using at most $k$ edges. (2) Negative Cycle Detection: You must verify the final check (the $|V|$-th iteration). Proving that if any distance can still be improved after $|V|-1$ iterations, a negative cycle effectively exists. (3) Upper Bound Correctness: Throughout the execution, you must prove that dist[v] is always an upper bound on the shortest path (i.e., dist[v] always corresponds to some real path, even if not yet optimal).

// <preamble>
// Define Option datatype since it is not built-in
datatype Option<T> = Some(value: T) | None

datatype WeightedGraph = WeightedGraph(
    // Adjacency list: adj[u] contains list of (neighbor, weight)
    // u ranges from 0 to size - 1.
    // Weight is int (was i64 in Verus)
    adj: seq<seq<(int, int)>>
)

// Helper predicates/functions for the Graph datatype
ghost function size(g: WeightedGraph): int {
    |g.adj|
}

ghost function view(g: WeightedGraph): seq<seq<(int, int)>> {
    g.adj
}

ghost predicate well_formed(g: WeightedGraph) {
    // 1. Basic Bounds checks
    (forall u: int, i: int :: 
        0 <= u < |g.adj| && 0 <= i < |g.adj[u]| 
        ==> 
        0 <= g.adj[u][i].0 < |g.adj|)
    &&
    // 2. SIMPLE GRAPH CONSTRAINT: No multigraphs allowed.
    // For any node u, all outgoing edges must have distinct targets.
    // This ensures get_edge_weight is deterministic.
    (forall u: int, i: int, j: int :: 
        0 <= u < |g.adj| 
        && 0 <= i < |g.adj[u]| 
        && 0 <= j < |g.adj[u]| 
        && i != j
        ==> 
        g.adj[u][i].0 != g.adj[u][j].0)
}

// --- Weighted Graph Definitions ---

ghost predicate has_edge(g: seq<seq<(int, int)>>, u: int, v: int, w: int) {
    exists i: int :: 
        0 <= u < |g| 
        && 0 <= i < |g[u]| 
        && g[u][i].0 == v
        && g[u][i].1 == w
}

ghost predicate connected(g: seq<seq<(int, int)>>, u: int, v: int) {
    exists w: int :: has_edge(g, u, v, w)
}

ghost predicate is_path(g: seq<seq<(int, int)>>, p: seq<int>) {
    |p| > 0
    && (forall i: int :: 
          0 <= i < |p| - 1 ==> connected(g, p[i], p[i+1]))
}

// Helper to safely extract weight using the epsilon operator
// Because well_formed enforces unique targets, this is uniquely defined for valid edges.
ghost function get_edge_weight(g: seq<seq<(int, int)>>, u: int, v: int): int {
    if exists w :: has_edge(g, u, v, w) then
        var w :| has_edge(g, u, v, w); w
    else 
        0 
}

ghost function path_weight(g: seq<seq<(int, int)>>, p: seq<int>): int 
    decreases |p|
{
    if |p| < 2 then 
        0
    else 
        // Recursive step: weight of prefix + weight of last edge
        path_weight(g, p[..|p| - 1]) + get_edge_weight(g, p[|p| - 2], p[|p| - 1])
}

ghost predicate is_shortest_dist(g: seq<seq<(int, int)>>, start: int, end: int, d: int) {
    && (exists p: seq<int> {:trigger is_path(g, p)} :: 
            is_path(g, p) 
            && p[0] == start 
            && p[|p| - 1] == end 
            && path_weight(g, p) == d
       )
    && (forall p: seq<int> {:trigger is_path(g, p)} :: 
            is_path(g, p) && p[0] == start && p[|p| - 1] == end 
            ==> path_weight(g, p) >= d
       )
}

ghost predicate has_negative_cycle(g: seq<seq<(int, int)>>) {
    exists p: seq<int> {:trigger is_path(g, p)} :: 
        is_path(g, p) 
        && |p| > 1 
        && p[0] == p[|p| - 1] 
        && path_weight(g, p) < 0
}

// We enforce hard limits:
// 1. Graph size <= 100,000
// 2. Edge weights <= 100,000 (absolute value)
ghost predicate weights_and_size_bounded(g: seq<seq<(int, int)>>) {
    && |g| <= 100_000
    && (forall u: int, v: int, w: int :: 
            has_edge(g, u, v, w) ==> (w >= -100_000 && w <= 100_000))
}
// </preamble>

// Following is the block for potential helper specifications
// <helpers>

// </helpers>

// Following is the block for proofs of lemmas
// <proofs>

// </proofs>

// Following is the block for the main specification
// <spec>
// Bellman-Ford Algorithm
method bellman_ford(graph: WeightedGraph, start: int) returns (res: Option<seq<Option<int>>>)
    requires well_formed(graph)
    requires 0 <= start < size(graph)
    requires weights_and_size_bounded(view(graph))
    ensures match res {
        case Some(dists) => 
            |dists| == size(graph) &&
            forall v: int :: 0 <= v < size(graph) ==> (
                match dists[v] {
                    case Some(d) => is_shortest_dist(view(graph), start, v, d)
                    case None => forall p: seq<int> {:trigger is_path(view(graph), p)} :: 
                                    is_path(view(graph), p) && p[0] == start && p[|p| - 1] == v ==> false
                }
            )
        case None => 
            has_negative_cycle(view(graph))
    }
// </spec>
// <code>
{
    // Implement and verify the Bellman-Ford algorithm here
}
// </code>