Problem: scc_tarjan

our task is to implement Tarjan's Algorithm to find the Strongly Connected Components (SCCs) of a directed graph and verify its functional correctness in Dafny. Preconditions: You can assume the graph contains at most 1,000 nodes. The graph is directed and represented as an adjacency list. Requirements: Implement the function find_sccs which returns a seq<seq<int>>. Output Format: The result must be a sequence of sequences, where each inner sequence represents one Strongly Connected Component. Partition: The set of returned components must form a partition of the graph vertices. Every node in the graph must appear in exactly one inner sequence. Single Nodes: A single node that is part of no cycle (or only a self-loop) is considered a valid SCC of size 1. Your output must include these "trivial" components. Strong Connectivity: For every component returned, and for every pair of nodes $(u, v)$ within that component, there must exist a path $u \rightsquigarrow v$ AND a path $v \rightsquigarrow u$. Maximality: The components must be maximal. This means no two components can be merged to form a larger strongly connected set. Formally, if there is a path from Component A to Component B, there must be no path from Component B back to Component A (The "Condensation Graph" must be a DAG). Verification Challenges: (1) DFS Invariants: Tarjan's algorithm uses discovery_time and low_link values. You must prove that low_link[u] correctly identifies the highest ancestor reachable from u in the DFS tree. (2) Stack Property: You must prove that the stack used during DFS correctly tracks the current path and potential "open" SCCs. Specifically, you must verify that popping from the stack when low_link[u] == discovery_time[u] yields exactly the nodes of the newly found SCC. (3) Maximality Proof: This is the most difficult step. You must prove that the algorithm does not "break" a large component into smaller valid pieces. The proof requires showing that the DFS search order ensures that by the time a root node is popped, all its descendants have been fully processed and assigned to their correct components.

// Following is the block for necessary definitions
// <preamble>
datatype Graph = Graph(adj: seq<seq<int>>)

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

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

ghost predicate well_formed(g: Graph) {
    forall u: int, i: int :: 
        0 <= u < |g.adj| && 0 <= i < |g.adj[u]| 
        ==> 
        0 <= g.adj[u][i] < |g.adj|
}

// --- GRAPH PATH DEFINITIONS ---

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

ghost predicate path_valid(g: seq<seq<int>>, p: seq<int>) {
    forall k: int :: 0 <= k < |p| - 1 ==>
        has_edge(g, p[k], p[k+1])
}

ghost predicate has_path(g: seq<seq<int>>, u: int, v: int) {
    exists p: seq<int> {:trigger path_valid(g, p)} :: 
        |p| > 0 && p[0] == u && p[|p| - 1] == v 
        && path_valid(g, p)
}

// --- SCC DEFINITIONS ---

ghost predicate seq_contains(s: seq<int>, v: int) {
    exists i: int :: 0 <= i < |s| && s[i] == v
}

// 1. Internal Strong Connectivity
ghost predicate is_strongly_connected(g: seq<seq<int>>, comp: seq<int>) {
    && |comp| > 0
    && (forall i: int :: 0 <= i < |comp| ==> 0 <= comp[i] < |g|)
    && (forall u: int, v: int :: 
            seq_contains(comp, u) && seq_contains(comp, v) ==> 
            (has_path(g, u, v) && has_path(g, v, u))
       )
}

// 2. Partition Property
ghost predicate is_covered(sccs: seq<seq<int>>, u: int) {
    exists i: int :: 0 <= i < |sccs| && seq_contains(sccs[i], u)
}

ghost predicate are_disjoint(sccs: seq<seq<int>>) {
    forall i: int, j: int, u: int :: 
        0 <= i < |sccs| && 0 <= j < |sccs| && i != j ==>
        !(seq_contains(sccs[i], u) && seq_contains(sccs[j], u))
}

ghost predicate is_partition(g: seq<seq<int>>, sccs: seq<seq<int>>) {
    && are_disjoint(sccs)
    && (forall u: int :: 0 <= u < |g| ==> is_covered(sccs, u))
}

// 3. Maximality (Condensation DAG Property)

// NEW HELPER: Is there a path from ANY node in component i to ANY node in component j?
// FIXED: Added requires clause to prevent index out of range
ghost predicate scc_has_path(g: seq<seq<int>>, sccs: seq<seq<int>>, i: int, j: int) 
    requires 0 <= i < |sccs|
    requires 0 <= j < |sccs|
{
    exists u: int, v: int :: 
        seq_contains(sccs[i], u) 
        && seq_contains(sccs[j], v) 
        && has_path(g, u, v)
}

ghost predicate is_maximal_scc_structure(g: seq<seq<int>>, sccs: seq<seq<int>>) {
    forall i: int, j: int :: 
        0 <= i < |sccs| && 0 <= j < |sccs| && i != j ==>
        // If there is a path from SCC i to SCC j, there must NOT be a path back
        (scc_has_path(g, sccs, i, j) ==> !scc_has_path(g, sccs, j, i))
}

ghost predicate is_valid_scc_result(g: seq<seq<int>>, sccs: seq<seq<int>>) {
    && is_partition(g, sccs)
    && (forall i: int :: 0 <= i < |sccs| ==> is_strongly_connected(g, sccs[i]))
    && is_maximal_scc_structure(g, sccs)
}

// Bounds Check
ghost predicate size_bounded(g: seq<seq<int>>) {
    |g| <= 1000
}
// </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>
// PROBLEM: Tarjan's SCC Algorithm
method find_sccs(graph: Graph) returns (sccs: seq<seq<int>>)
    requires well_formed(graph)
    requires size_bounded(view(graph))
    ensures is_valid_scc_result(view(graph), sccs)
// </spec>
// <code>
{
    // Implement and verify Tarjan's SCC algorithm here.
}
// </code>