package support; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Reference implementations of InferenceGraph.Node.closure() — defective and fixed. * * DEFECT 0002b (Infer.java:1747): closure() recomputes the DFS reachability * set on every call — no caching. Called K times from canInfluence() within * the O(N²) loop in buildStuckGraph(). With the O(N) findNode scan (0002a), * the combined total = O(N³). * * Fix: cache the closure result after the first computation. * * Exact node-visit counts when calling closure(N0) K times on a V-node chain: * defective: K * V (each of K calls runs a full O(V) DFS) * fixed: V + (K - 1) (first call = V visits; each cache hit = 1) * * Derivation: * Chain N0→N1→...→N(V-1). closure(N0) = all V nodes. * Defective: K independent DFS traversals each visiting V nodes = K*V. * Fixed: 1st call DFS visits V nodes and stores result. Calls 2..K each * find the cached set in one check: 1 visit each. Total = V + (K-1). * * Growth when K doubles (V fixed): defective ≈2x (linear in K), fixed ≈1x * (nearly constant: (V+2K-1)/(V+K-1) → 1 as V grows or K≪V). */ public class ClosureAlgorithm { public static class Node { public final String label; public final List deps = new ArrayList<>(); // used by fixed version Set cachedClosure = null; public Node(String label) { this.label = label; } public void addDep(Node dep) { deps.add(dep); } @Override public String toString() { return label; } } public static class Result { /** The closure set returned on each of the K calls (all identical). */ public final Set closure; /** * Total node visits across all K calls. * Defective: K * V. Fixed: V + (K-1). */ public final long nodeVisits; Result(Set closure, long nodeVisits) { this.closure = closure; this.nodeVisits = nodeVisits; } } // ─── DEFECTIVE: uncached DFS on every call ─────────────────────────────── // Mirrors Infer.java:1747 — computes closure fresh each time it is called. public static Result closureDefective(Node start, int calls) { long[] visits = {0}; Set last = null; for (int i = 0; i < calls; i++) { Set closure = new HashSet<>(); dfs(start, closure, visits); last = closure; } return new Result(last, visits[0]); } private static void dfs(Node n, Set visited, long[] visits) { if (visited.contains(n)) return; visits[0]++; visited.add(n); for (Node dep : n.deps) dfs(dep, visited, visits); } // ─── FIXED: cached DFS ─────────────────────────────────────────────────── // First call runs the DFS and stores the result on the node. // Subsequent calls find the cached result in one check. public static Result closureFixed(Node start, int calls) { long[] visits = {0}; Set last = null; for (int i = 0; i < calls; i++) { last = closureCached(start, visits); } return new Result(last, visits[0]); } private static Set closureCached(Node n, long[] visits) { if (n.cachedClosure != null) { visits[0]++; // one operation: read the cached field return n.cachedClosure; } Set closure = new HashSet<>(); dfs(n, closure, visits); // counts V visits n.cachedClosure = closure; return closure; } // ─── Factory ───────────────────────────────────────────────────────────── /** * Linear chain of V nodes: N0→N1→...→N(V-1). * closure(N0) = all V nodes. * Returns N0 (the start node for closure queries). * * Creates fresh nodes — call this separately for defective vs fixed tests * so the fixed cache does not leak between test runs. */ public static Node buildLinearChain(int v) { List nodes = new ArrayList<>(); for (int i = 0; i < v; i++) nodes.add(new Node("N" + i)); for (int i = 0; i < v - 1; i++) nodes.get(i).addDep(nodes.get(i + 1)); return nodes.get(0); } }